oxwm

https://git.tonybtw.com/oxwm.git git://git.tonybtw.com/oxwm.git
10,158 bytes raw
1
use super::{Overlay, OverlayBase};
2
use crate::bar::font::Font;
3
use crate::errors::X11Error;
4
use crate::keyboard::KeyAction;
5
use crate::keyboard::handlers::{KeyBinding, KeyPress};
6
use std::time::Instant;
7
use x11rb::connection::Connection;
8
use x11rb::protocol::xproto::*;
9
use x11rb::rust_connection::RustConnection;
10
11
const PADDING: i16 = 24;
12
const KEY_ACTION_SPACING: i16 = 20;
13
const LINE_SPACING: i16 = 8;
14
const BORDER_WIDTH: u16 = 4;
15
const BORDER_COLOR: u32 = 0x7fccff;
16
const TITLE_BOTTOM_MARGIN: i16 = 20;
17
const INPUT_SUPPRESS_MS: u128 = 200;
18
19
pub struct KeybindOverlay {
20
    base: OverlayBase,
21
    keybindings: Vec<(String, String)>,
22
    key_bg_color: u32,
23
    modkey: KeyButMask,
24
    last_shown_at: Option<Instant>,
25
    max_key_width: u16,
26
}
27
28
impl KeybindOverlay {
29
    pub fn new(
30
        connection: &RustConnection,
31
        screen: &Screen,
32
        screen_num: usize,
33
        display: *mut x11::xlib::Display,
34
        modkey: KeyButMask,
35
    ) -> Result<Self, X11Error> {
36
        let base = OverlayBase::new(
37
            connection,
38
            screen,
39
            screen_num,
40
            display,
41
            800,
42
            600,
43
            BORDER_WIDTH,
44
            BORDER_COLOR,
45
            0x1a1a1a,
46
            0xffffff,
47
        )?;
48
49
        Ok(KeybindOverlay {
50
            base,
51
            keybindings: Vec::new(),
52
            key_bg_color: 0x2a2a2a,
53
            modkey,
54
            last_shown_at: None,
55
            max_key_width: 0,
56
        })
57
    }
58
59
    pub fn show(
60
        &mut self,
61
        connection: &RustConnection,
62
        font: &Font,
63
        keybindings: &[KeyBinding],
64
        monitor_x: i16,
65
        monitor_y: i16,
66
        screen_width: u16,
67
        screen_height: u16,
68
    ) -> Result<(), X11Error> {
69
        self.keybindings = self.collect_keybindings(keybindings);
70
71
        let title = "Important Keybindings";
72
        let title_width = font.text_width(title);
73
74
        let mut max_key_width = 0u16;
75
        let mut max_action_width = 0u16;
76
77
        for (key, action) in &self.keybindings {
78
            let key_width = font.text_width(key);
79
            let action_width = font.text_width(action);
80
            if key_width > max_key_width {
81
                max_key_width = key_width;
82
            }
83
            if action_width > max_action_width {
84
                max_action_width = action_width;
85
            }
86
        }
87
88
        let content_width = max_key_width + KEY_ACTION_SPACING as u16 + max_action_width;
89
        let min_width = title_width.max(content_width);
90
91
        let width = min_width + (PADDING as u16 * 2);
92
93
        let line_height = font.height() + LINE_SPACING as u16;
94
        let title_height = font.height() + TITLE_BOTTOM_MARGIN as u16;
95
        let height =
96
            title_height + (self.keybindings.len() as u16 * line_height) + (PADDING as u16 * 2);
97
98
        let x = monitor_x + ((screen_width - width) / 2) as i16;
99
        let y = monitor_y + ((screen_height - height) / 2) as i16;
100
101
        self.base.configure(connection, x, y, width, height)?;
102
103
        self.last_shown_at = Some(Instant::now());
104
        self.max_key_width = max_key_width;
105
106
        self.base.show(connection)?;
107
108
        self.draw(connection, font)?;
109
110
        Ok(())
111
    }
112
113
    pub fn toggle(
114
        &mut self,
115
        connection: &RustConnection,
116
        font: &Font,
117
        keybindings: &[KeyBinding],
118
        monitor_x: i16,
119
        monitor_y: i16,
120
        screen_width: u16,
121
        screen_height: u16,
122
    ) -> Result<(), X11Error> {
123
        if self.base.is_visible {
124
            self.hide(connection)?;
125
        } else {
126
            self.show(connection, font, keybindings, monitor_x, monitor_y, screen_width, screen_height)?;
127
        }
128
        Ok(())
129
    }
130
131
    pub fn should_suppress_input(&self) -> bool {
132
        if let Some(shown_at) = self.last_shown_at {
133
            shown_at.elapsed().as_millis() < INPUT_SUPPRESS_MS
134
        } else {
135
            false
136
        }
137
    }
138
139
    fn collect_keybindings(&self, keybindings: &[KeyBinding]) -> Vec<(String, String)> {
140
        let mut result = Vec::new();
141
142
        let priority_actions = [
143
            KeyAction::ShowKeybindOverlay,
144
            KeyAction::Quit,
145
            KeyAction::Restart,
146
            KeyAction::KillClient,
147
            KeyAction::Spawn,
148
            KeyAction::SpawnTerminal,
149
            KeyAction::ToggleFullScreen,
150
            KeyAction::ToggleFloating,
151
            KeyAction::CycleLayout,
152
            KeyAction::FocusStack,
153
            KeyAction::ViewTag,
154
        ];
155
156
        for &action in &priority_actions {
157
            let binding = keybindings
158
                .iter()
159
                .filter(|kb| kb.func == action)
160
                .min_by_key(|kb| kb.keys.len());
161
162
            if let Some(binding) = binding {
163
                if !binding.keys.is_empty() {
164
                    let key_str = self.format_key_combo(&binding.keys[0]);
165
                    let action_str = self.action_description(binding);
166
                    result.push((key_str, action_str));
167
                }
168
            }
169
        }
170
171
        result
172
    }
173
174
    fn format_key_combo(&self, key: &KeyPress) -> String {
175
        let mut parts = Vec::new();
176
177
        for modifier in &key.modifiers {
178
            let mod_str = match *modifier {
179
                m if m == self.modkey => "Mod",
180
                KeyButMask::SHIFT => "Shift",
181
                KeyButMask::CONTROL => "Ctrl",
182
                KeyButMask::MOD1 => "Alt",
183
                KeyButMask::MOD4 => "Super",
184
                _ => continue,
185
            };
186
            parts.push(mod_str.to_string());
187
        }
188
189
        parts.push(crate::keyboard::keysyms::format_keysym(key.keysym));
190
191
        parts.join(" + ")
192
    }
193
194
    fn action_description(&self, binding: &KeyBinding) -> String {
195
        use crate::keyboard::Arg;
196
197
        match binding.func {
198
            KeyAction::ShowKeybindOverlay => "Show This Keybind Help".to_string(),
199
            KeyAction::Quit => "Quit Window Manager".to_string(),
200
            KeyAction::Restart => "Restart Window Manager".to_string(),
201
            KeyAction::Recompile => "Recompile Window Manager".to_string(),
202
            KeyAction::KillClient => "Close Focused Window".to_string(),
203
            KeyAction::Spawn => match &binding.arg {
204
                Arg::Str(cmd) => format!("Launch: {}", cmd),
205
                Arg::Array(arr) if !arr.is_empty() => format!("Launch: {}", arr[0]),
206
                _ => "Launch Program".to_string(),
207
            },
208
            KeyAction::SpawnTerminal => "Launch Terminal".to_string(),
209
            KeyAction::FocusStack => "Focus Next/Previous Window".to_string(),
210
            KeyAction::MoveStack => "Move Window Up/Down Stack".to_string(),
211
            KeyAction::ViewTag => match &binding.arg {
212
                Arg::Int(n) => format!("View Workspace {}", n),
213
                _ => "View Workspace".to_string(),
214
            },
215
            KeyAction::ToggleView => match &binding.arg {
216
                Arg::Int(n) => format!("Toggle View Workspace {}", n),
217
                _ => "Toggle View Workspace".to_string(),
218
            },
219
            KeyAction::MoveToTag => "Move Window to Workspace".to_string(),
220
            KeyAction::ToggleTag => "Toggle Window on Workspace".to_string(),
221
            KeyAction::ToggleGaps => "Toggle Window Gaps".to_string(),
222
            KeyAction::ToggleFullScreen => "Toggle Fullscreen Mode".to_string(),
223
            KeyAction::ToggleFloating => "Toggle Floating Mode".to_string(),
224
            KeyAction::ChangeLayout => "Change Layout".to_string(),
225
            KeyAction::CycleLayout => "Cycle Through Layouts".to_string(),
226
            KeyAction::FocusMonitor => "Focus Next Monitor".to_string(),
227
            KeyAction::TagMonitor => "Send Window to Monitor".to_string(),
228
            KeyAction::SetMasterFactor => "Adjust Master Area Size".to_string(),
229
            KeyAction::IncNumMaster => "Adjust Number of Master Windows".to_string(),
230
            KeyAction::None => "No Action".to_string(),
231
        }
232
    }
233
}
234
235
impl Overlay for KeybindOverlay {
236
    fn window(&self) -> Window {
237
        self.base.window
238
    }
239
240
    fn is_visible(&self) -> bool {
241
        self.base.is_visible
242
    }
243
244
    fn hide(&mut self, connection: &RustConnection) -> Result<(), X11Error> {
245
        self.base.hide(connection)?;
246
        self.last_shown_at = None;
247
        self.keybindings.clear();
248
        Ok(())
249
    }
250
251
    fn draw(&self, connection: &RustConnection, font: &Font) -> Result<(), X11Error> {
252
        if !self.base.is_visible {
253
            return Ok(());
254
        }
255
256
        self.base.draw_background(connection)?;
257
258
        let title = "Important Keybindings";
259
        let title_width = font.text_width(title);
260
        let title_x = ((self.base.width - title_width) / 2) as i16;
261
        let title_y = PADDING + font.ascent();
262
263
        self.base
264
            .font_draw
265
            .draw_text(font, self.base.foreground_color, title_x, title_y, title);
266
267
        let line_height = font.height() + LINE_SPACING as u16;
268
        let mut y = PADDING + font.height() as i16 + TITLE_BOTTOM_MARGIN + font.ascent();
269
270
        for (key, action) in &self.keybindings {
271
            let key_width = font.text_width(key);
272
            let key_x = PADDING;
273
274
            connection.change_gc(
275
                self.base.graphics_context,
276
                &ChangeGCAux::new().foreground(self.key_bg_color),
277
            )?;
278
            connection.poly_fill_rectangle(
279
                self.base.window,
280
                self.base.graphics_context,
281
                &[Rectangle {
282
                    x: key_x - 4,
283
                    y: y - font.ascent() - 2,
284
                    width: key_width + 8,
285
                    height: font.height() + 4,
286
                }],
287
            )?;
288
289
            self.base
290
                .font_draw
291
                .draw_text(font, self.base.foreground_color, key_x, y, key);
292
293
            let action_x = PADDING + self.max_key_width as i16 + KEY_ACTION_SPACING;
294
            self.base
295
                .font_draw
296
                .draw_text(font, self.base.foreground_color, action_x, y, action);
297
298
            y += line_height as i16;
299
        }
300
301
        self.base.font_draw.flush();
302
303
        connection.flush()?;
304
305
        Ok(())
306
    }
307
}