oxwm

https://git.tonybtw.com/oxwm.git git://git.tonybtw.com/oxwm.git
10,244 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.is_visible = true;
107
        self.draw(connection, font)?;
108
109
        self.base.show(connection)?;
110
111
        Ok(())
112
    }
113
114
    pub fn toggle(
115
        &mut self,
116
        connection: &RustConnection,
117
        font: &Font,
118
        keybindings: &[KeyBinding],
119
        monitor_x: i16,
120
        monitor_y: i16,
121
        screen_width: u16,
122
        screen_height: u16,
123
    ) -> Result<(), X11Error> {
124
        if self.base.is_visible {
125
            self.hide(connection)?;
126
        } else {
127
            self.show(
128
                connection,
129
                font,
130
                keybindings,
131
                monitor_x,
132
                monitor_y,
133
                screen_width,
134
                screen_height,
135
            )?;
136
        }
137
        Ok(())
138
    }
139
140
    pub fn should_suppress_input(&self) -> bool {
141
        if let Some(shown_at) = self.last_shown_at {
142
            shown_at.elapsed().as_millis() < INPUT_SUPPRESS_MS
143
        } else {
144
            false
145
        }
146
    }
147
148
    fn collect_keybindings(&self, keybindings: &[KeyBinding]) -> Vec<(String, String)> {
149
        let mut result = Vec::new();
150
151
        let priority_actions = [
152
            KeyAction::ShowKeybindOverlay,
153
            KeyAction::Quit,
154
            KeyAction::Restart,
155
            KeyAction::KillClient,
156
            KeyAction::Spawn,
157
            KeyAction::SpawnTerminal,
158
            KeyAction::ToggleFullScreen,
159
            KeyAction::ToggleFloating,
160
            KeyAction::CycleLayout,
161
            KeyAction::FocusStack,
162
            KeyAction::ViewTag,
163
        ];
164
165
        for &action in &priority_actions {
166
            let binding = keybindings
167
                .iter()
168
                .filter(|kb| kb.func == action)
169
                .min_by_key(|kb| kb.keys.len());
170
171
            if let Some(binding) = binding {
172
                if !binding.keys.is_empty() {
173
                    let key_str = self.format_key_combo(&binding.keys[0]);
174
                    let action_str = self.action_description(binding);
175
                    result.push((key_str, action_str));
176
                }
177
            }
178
        }
179
180
        result
181
    }
182
183
    fn format_key_combo(&self, key: &KeyPress) -> String {
184
        let mut parts = Vec::new();
185
186
        for modifier in &key.modifiers {
187
            let mod_str = match *modifier {
188
                m if m == self.modkey => "Mod",
189
                KeyButMask::SHIFT => "Shift",
190
                KeyButMask::CONTROL => "Ctrl",
191
                KeyButMask::MOD1 => "Alt",
192
                KeyButMask::MOD4 => "Super",
193
                _ => continue,
194
            };
195
            parts.push(mod_str.to_string());
196
        }
197
198
        parts.push(crate::keyboard::keysyms::format_keysym(key.keysym));
199
200
        parts.join(" + ")
201
    }
202
203
    fn action_description(&self, binding: &KeyBinding) -> String {
204
        use crate::keyboard::Arg;
205
206
        match binding.func {
207
            KeyAction::ShowKeybindOverlay => "Show This Keybind Help".to_string(),
208
            KeyAction::Quit => "Quit Window Manager".to_string(),
209
            KeyAction::Restart => "Restart Window Manager".to_string(),
210
            KeyAction::KillClient => "Close Focused Window".to_string(),
211
            KeyAction::Spawn => match &binding.arg {
212
                Arg::Str(cmd) => format!("Launch: {}", cmd),
213
                Arg::Array(arr) if !arr.is_empty() => format!("Launch: {}", arr[0]),
214
                _ => "Launch Program".to_string(),
215
            },
216
            KeyAction::SpawnTerminal => "Launch Terminal".to_string(),
217
            KeyAction::FocusStack => "Focus Next/Previous Window".to_string(),
218
            KeyAction::MoveStack => "Move Window Up/Down Stack".to_string(),
219
            KeyAction::ViewTag => match &binding.arg {
220
                Arg::Int(n) => format!("View Workspace {}", n),
221
                _ => "View Workspace".to_string(),
222
            },
223
            KeyAction::ToggleView => match &binding.arg {
224
                Arg::Int(n) => format!("Toggle View Workspace {}", n),
225
                _ => "Toggle View Workspace".to_string(),
226
            },
227
            KeyAction::MoveToTag => "Move Window to Workspace".to_string(),
228
            KeyAction::ToggleTag => "Toggle Window on Workspace".to_string(),
229
            KeyAction::ToggleGaps => "Toggle Window Gaps".to_string(),
230
            KeyAction::ToggleFullScreen => "Toggle Fullscreen Mode".to_string(),
231
            KeyAction::ToggleFloating => "Toggle Floating Mode".to_string(),
232
            KeyAction::ChangeLayout => "Change Layout".to_string(),
233
            KeyAction::CycleLayout => "Cycle Through Layouts".to_string(),
234
            KeyAction::FocusMonitor => "Focus Next Monitor".to_string(),
235
            KeyAction::TagMonitor => "Send Window to Monitor".to_string(),
236
            KeyAction::SetMasterFactor => "Adjust Master Area Size".to_string(),
237
            KeyAction::IncNumMaster => "Adjust Number of Master Windows".to_string(),
238
            KeyAction::None => "No Action".to_string(),
239
        }
240
    }
241
}
242
243
impl Overlay for KeybindOverlay {
244
    fn window(&self) -> Window {
245
        self.base.window
246
    }
247
248
    fn is_visible(&self) -> bool {
249
        self.base.is_visible
250
    }
251
252
    fn hide(&mut self, connection: &RustConnection) -> Result<(), X11Error> {
253
        self.base.hide(connection)?;
254
        self.last_shown_at = None;
255
        self.keybindings.clear();
256
        Ok(())
257
    }
258
259
    fn draw(&self, connection: &RustConnection, font: &Font) -> Result<(), X11Error> {
260
        if !self.base.is_visible {
261
            return Ok(());
262
        }
263
264
        self.base.draw_background(connection)?;
265
266
        let title = "Important Keybindings";
267
        let title_width = font.text_width(title);
268
        let title_x = ((self.base.width - title_width) / 2) as i16;
269
        let title_y = PADDING + font.ascent();
270
271
        self.base
272
            .font_draw
273
            .draw_text(font, self.base.foreground_color, title_x, title_y, title);
274
275
        let line_height = font.height() + LINE_SPACING as u16;
276
        let mut y = PADDING + font.height() as i16 + TITLE_BOTTOM_MARGIN + font.ascent();
277
278
        for (key, action) in &self.keybindings {
279
            let key_width = font.text_width(key);
280
            let key_x = PADDING;
281
282
            connection.change_gc(
283
                self.base.graphics_context,
284
                &ChangeGCAux::new().foreground(self.key_bg_color),
285
            )?;
286
            connection.poly_fill_rectangle(
287
                self.base.window,
288
                self.base.graphics_context,
289
                &[Rectangle {
290
                    x: key_x - 4,
291
                    y: y - font.ascent() - 2,
292
                    width: key_width + 8,
293
                    height: font.height() + 4,
294
                }],
295
            )?;
296
297
            self.base
298
                .font_draw
299
                .draw_text(font, self.base.foreground_color, key_x, y, key);
300
301
            let action_x = PADDING + self.max_key_width as i16 + KEY_ACTION_SPACING;
302
            self.base
303
                .font_draw
304
                .draw_text(font, self.base.foreground_color, action_x, y, action);
305
306
            y += line_height as i16;
307
        }
308
309
        connection.flush()?;
310
        self.base.font_draw.sync();
311
312
        Ok(())
313
    }
314
}