oxwm

https://git.tonybtw.com/oxwm.git git://git.tonybtw.com/oxwm.git
12,842 bytes raw
1
use super::blocks::Block;
2
use super::font::{DrawingSurface, Font};
3
use crate::Config;
4
use crate::errors::X11Error;
5
use std::time::Instant;
6
use x11rb::COPY_DEPTH_FROM_PARENT;
7
use x11rb::connection::Connection;
8
use x11rb::protocol::xproto::*;
9
use x11rb::rust_connection::RustConnection;
10
11
pub struct Bar {
12
    window: Window,
13
    width: u16,
14
    height: u16,
15
    graphics_context: Gcontext,
16
    surface: DrawingSurface,
17
18
    tag_widths: Vec<u16>,
19
    needs_redraw: bool,
20
21
    blocks: Vec<Box<dyn Block>>,
22
    block_last_updates: Vec<Instant>,
23
    block_underlines: Vec<bool>,
24
    status_text: String,
25
26
    tags: Vec<String>,
27
    scheme_normal: crate::ColorScheme,
28
    scheme_occupied: crate::ColorScheme,
29
    scheme_selected: crate::ColorScheme,
30
    scheme_urgent: crate::ColorScheme,
31
}
32
33
impl Bar {
34
    pub fn new(
35
        connection: &RustConnection,
36
        screen: &Screen,
37
        screen_num: usize,
38
        config: &Config,
39
        display: *mut x11::xlib::Display,
40
        font: &Font,
41
        x: i16,
42
        y: i16,
43
        width: u16,
44
    ) -> Result<Self, X11Error> {
45
        let window = connection.generate_id()?;
46
        let graphics_context = connection.generate_id()?;
47
48
        let height = (font.height() as f32 * 1.4) as u16;
49
50
        connection.create_window(
51
            COPY_DEPTH_FROM_PARENT,
52
            window,
53
            screen.root,
54
            x,
55
            y,
56
            width,
57
            height,
58
            0,
59
            WindowClass::INPUT_OUTPUT,
60
            screen.root_visual,
61
            &CreateWindowAux::new()
62
                .background_pixel(config.scheme_normal.background)
63
                .event_mask(EventMask::EXPOSURE | EventMask::BUTTON_PRESS)
64
                .override_redirect(1),
65
        )?;
66
67
        connection.create_gc(
68
            graphics_context,
69
            window,
70
            &CreateGCAux::new()
71
                .foreground(config.scheme_normal.foreground)
72
                .background(config.scheme_normal.background),
73
        )?;
74
75
        connection.map_window(window)?;
76
        connection.flush()?;
77
78
        let visual = unsafe { x11::xlib::XDefaultVisual(display, screen_num as i32) };
79
        let colormap = unsafe { x11::xlib::XDefaultColormap(display, screen_num as i32) };
80
81
        let surface = DrawingSurface::new(
82
            display,
83
            window as x11::xlib::Drawable,
84
            width as u32,
85
            height as u32,
86
            visual,
87
            colormap,
88
        )?;
89
90
        let horizontal_padding = (font.height() as f32 * 0.4) as u16;
91
92
        let tag_widths = config
93
            .tags
94
            .iter()
95
            .map(|tag| {
96
                let text_width = font.text_width(tag);
97
                text_width + (horizontal_padding * 2)
98
            })
99
            .collect();
100
101
        let blocks: Vec<Box<dyn Block>> = config
102
            .status_blocks
103
            .iter()
104
            .map(|block_config| block_config.to_block())
105
            .collect();
106
107
        let block_underlines: Vec<bool> = config
108
            .status_blocks
109
            .iter()
110
            .map(|block_config| block_config.underline)
111
            .collect();
112
113
        let block_last_updates = vec![Instant::now(); blocks.len()];
114
115
        Ok(Bar {
116
            window,
117
            width,
118
            height,
119
            graphics_context,
120
            surface,
121
            tag_widths,
122
            needs_redraw: true,
123
            blocks,
124
            block_last_updates,
125
            block_underlines,
126
            status_text: String::new(),
127
            tags: config.tags.clone(),
128
            scheme_normal: config.scheme_normal,
129
            scheme_occupied: config.scheme_occupied,
130
            scheme_selected: config.scheme_selected,
131
            scheme_urgent: config.scheme_urgent,
132
        })
133
    }
134
135
    pub fn window(&self) -> Window {
136
        self.window
137
    }
138
139
    pub fn height(&self) -> u16 {
140
        self.height
141
    }
142
143
    pub fn invalidate(&mut self) {
144
        self.needs_redraw = true;
145
    }
146
147
    pub fn update_blocks(&mut self) {
148
        let now = Instant::now();
149
        let mut changed = false;
150
151
        for (i, block) in self.blocks.iter_mut().enumerate() {
152
            let elapsed = now.duration_since(self.block_last_updates[i]);
153
154
            if elapsed >= block.interval() && block.content().is_ok() {
155
                self.block_last_updates[i] = now;
156
                changed = true;
157
            }
158
        }
159
160
        if changed {
161
            let mut parts = Vec::new();
162
            for block in &mut self.blocks {
163
                if let Ok(text) = block.content() {
164
                    parts.push(text);
165
                }
166
            }
167
            self.status_text = parts.join("");
168
            self.needs_redraw = true;
169
        }
170
    }
171
172
    pub fn draw(
173
        &mut self,
174
        connection: &RustConnection,
175
        font: &Font,
176
        display: *mut x11::xlib::Display,
177
        current_tags: u32,
178
        occupied_tags: u32,
179
        urgent_tags: u32,
180
        draw_blocks: bool,
181
        layout_symbol: &str,
182
        keychord_indicator: Option<&str>,
183
    ) -> Result<(), X11Error> {
184
        if !self.needs_redraw {
185
            return Ok(());
186
        }
187
188
        connection.change_gc(
189
            self.graphics_context,
190
            &ChangeGCAux::new().foreground(self.scheme_normal.background),
191
        )?;
192
        connection.flush()?;
193
194
        unsafe {
195
            let gc = x11::xlib::XCreateGC(display, self.surface.pixmap(), 0, std::ptr::null_mut());
196
            x11::xlib::XSetForeground(display, gc, self.scheme_normal.background as u64);
197
            x11::xlib::XFillRectangle(
198
                display,
199
                self.surface.pixmap(),
200
                gc,
201
                0,
202
                0,
203
                self.width as u32,
204
                self.height as u32,
205
            );
206
            x11::xlib::XFreeGC(display, gc);
207
        }
208
209
        let mut x_position: i16 = 0;
210
211
        for (tag_index, tag) in self.tags.iter().enumerate() {
212
            let tag_mask = 1 << tag_index;
213
            let is_selected = (current_tags & tag_mask) != 0;
214
            let is_occupied = (occupied_tags & tag_mask) != 0;
215
            let is_urgent = (urgent_tags & tag_mask) != 0;
216
217
            let tag_width = self.tag_widths[tag_index];
218
219
            let scheme = if is_selected {
220
                &self.scheme_selected
221
            } else if is_urgent {
222
                &self.scheme_urgent
223
            } else if is_occupied {
224
                &self.scheme_occupied
225
            } else {
226
                &self.scheme_normal
227
            };
228
229
            let text_width = font.text_width(tag);
230
            let text_x = x_position + ((tag_width - text_width) / 2) as i16;
231
232
            let top_padding = 4;
233
            let text_y = top_padding + font.ascent();
234
235
            self.surface
236
                .font_draw()
237
                .draw_text(font, scheme.foreground, text_x, text_y, tag);
238
239
            if is_selected || is_urgent {
240
                let font_height = font.height();
241
                let underline_height = font_height / 8;
242
                let bottom_gap = 3;
243
                let underline_y = self.height as i16 - underline_height as i16 - bottom_gap;
244
245
                let underline_padding = 4;
246
                let underline_width = tag_width - underline_padding;
247
                let underline_x = x_position + (underline_padding / 2) as i16;
248
249
                unsafe {
250
                    let gc = x11::xlib::XCreateGC(display, self.surface.pixmap(), 0, std::ptr::null_mut());
251
                    x11::xlib::XSetForeground(display, gc, scheme.underline as u64);
252
                    x11::xlib::XFillRectangle(
253
                        display,
254
                        self.surface.pixmap(),
255
                        gc,
256
                        underline_x as i32,
257
                        underline_y as i32,
258
                        underline_width as u32,
259
                        underline_height as u32,
260
                    );
261
                    x11::xlib::XFreeGC(display, gc);
262
                }
263
            }
264
265
            x_position += tag_width as i16;
266
        }
267
268
        x_position += 10;
269
270
        let text_x = x_position;
271
        let top_padding = 4;
272
        let text_y = top_padding + font.ascent();
273
274
        self.surface.font_draw().draw_text(
275
            font,
276
            self.scheme_normal.foreground,
277
            text_x,
278
            text_y,
279
            layout_symbol,
280
        );
281
282
        x_position += font.text_width(layout_symbol) as i16;
283
284
        if let Some(indicator) = keychord_indicator {
285
            x_position += 10;
286
287
            let text_x = x_position;
288
            let text_y = top_padding + font.ascent();
289
290
            self.surface.font_draw().draw_text(
291
                font,
292
                self.scheme_selected.foreground,
293
                text_x,
294
                text_y,
295
                indicator,
296
            );
297
        }
298
299
        if draw_blocks && !self.status_text.is_empty() {
300
            let padding = 10;
301
            let mut x_position = self.width as i16 - padding;
302
303
            for (i, block) in self.blocks.iter_mut().enumerate().rev() {
304
                if let Ok(text) = block.content() {
305
                    let text_width = font.text_width(&text);
306
                    x_position -= text_width as i16;
307
308
                    let top_padding = 4;
309
                    let text_y = top_padding + font.ascent();
310
311
                    self.surface
312
                        .font_draw()
313
                        .draw_text(font, block.color(), x_position, text_y, &text);
314
315
                    if self.block_underlines[i] {
316
                        let font_height = font.height();
317
                        let underline_height = font_height / 8;
318
                        let bottom_gap = 3;
319
                        let underline_y = self.height as i16 - underline_height as i16 - bottom_gap;
320
321
                        let underline_padding = 8;
322
                        let underline_width = text_width + underline_padding;
323
                        let underline_x = x_position - (underline_padding / 2) as i16;
324
325
                        unsafe {
326
                            let gc =
327
                                x11::xlib::XCreateGC(display, self.surface.pixmap(), 0, std::ptr::null_mut());
328
                            x11::xlib::XSetForeground(display, gc, block.color() as u64);
329
                            x11::xlib::XFillRectangle(
330
                                display,
331
                                self.surface.pixmap(),
332
                                gc,
333
                                underline_x as i32,
334
                                underline_y as i32,
335
                                underline_width as u32,
336
                                underline_height as u32,
337
                            );
338
                            x11::xlib::XFreeGC(display, gc);
339
                        }
340
                    }
341
                }
342
            }
343
        }
344
345
        unsafe {
346
            let gc = x11::xlib::XCreateGC(
347
                display,
348
                self.window as x11::xlib::Drawable,
349
                0,
350
                std::ptr::null_mut(),
351
            );
352
            x11::xlib::XCopyArea(
353
                display,
354
                self.surface.pixmap(),
355
                self.window as x11::xlib::Drawable,
356
                gc,
357
                0,
358
                0,
359
                self.width as u32,
360
                self.height as u32,
361
                0,
362
                0,
363
            );
364
            x11::xlib::XFreeGC(display, gc);
365
            x11::xlib::XSync(display, 1);
366
        }
367
368
        self.needs_redraw = false;
369
370
        Ok(())
371
    }
372
373
    pub fn handle_click(&self, click_x: i16) -> Option<usize> {
374
        let mut current_x_position = 0;
375
376
        for (tag_index, &tag_width) in self.tag_widths.iter().enumerate() {
377
            if click_x >= current_x_position && click_x < current_x_position + tag_width as i16 {
378
                return Some(tag_index);
379
            }
380
            current_x_position += tag_width as i16;
381
        }
382
        None
383
    }
384
385
    pub fn needs_redraw(&self) -> bool {
386
        self.needs_redraw
387
    }
388
389
    pub fn update_from_config(&mut self, config: &Config) {
390
        self.blocks = config
391
            .status_blocks
392
            .iter()
393
            .map(|block_config| block_config.to_block())
394
            .collect();
395
396
        self.block_underlines = config
397
            .status_blocks
398
            .iter()
399
            .map(|block_config| block_config.underline)
400
            .collect();
401
402
        self.block_last_updates = vec![Instant::now(); self.blocks.len()];
403
404
        self.tags = config.tags.clone();
405
        self.scheme_normal = config.scheme_normal;
406
        self.scheme_occupied = config.scheme_occupied;
407
        self.scheme_selected = config.scheme_selected;
408
        self.scheme_urgent = config.scheme_urgent;
409
410
        self.status_text.clear();
411
        self.needs_redraw = true;
412
    }
413
}