oxwm

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