oxwm

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