oxwm

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