summaryrefslogtreecommitdiff
path: root/src/clients/client.rs
blob: a7b1a8d3fa89477dfb67c9be46662923915935fd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
use xcb::{
    x::{
        ChangeProperty, ChangeWindowAttributes, ConfigWindow, ConfigureNotifyEvent,
        ConfigureWindow, Cw, EventMask, MapWindow, SendEvent, SendEventDest, Window,
    },
    Xid,
};

use crate::{config::BORDER_WIDTH, conn_info::Connection};

use super::hints;

/// Information about a single client / window
#[derive(Debug)]
pub struct Client {
    /// The corresponding X11 window
    window: Window,

    x: i16,
    y: i16,
    width: u16,
    height: u16,
    border_width: u16,
    mapped: bool,

    urgent: bool,
    never_focus: bool,
}

impl Client {
    pub const fn new(window: Window) -> Self {
        Self {
            window,
            x: 0,
            y: 0,
            width: 0,
            height: 0,
            border_width: BORDER_WIDTH,
            mapped: false,
            urgent: false,
            never_focus: false,
        }
    }

    /// Send a configure configure notify event with the current geometry.
    /// This function does not check for success, so `conn.flush()` should be called after.
    pub fn configure_notify(&self, conn: &Connection<'_>) {
        conn.send_request(&SendEvent {
            destination: SendEventDest::Window(self.window),
            event_mask: EventMask::STRUCTURE_NOTIFY,
            event: &ConfigureNotifyEvent::new(
                self.window,
                self.window,
                Window::none(),
                self.x,
                self.y,
                self.width,
                self.height,
                self.border_width,
                false,
            ),
            propagate: false,
        });
    }

    /// Set this client's geometry, also updating the X11 window if needed.
    /// This function does not check for success, so `conn.flush()` should be called after.
    pub fn set_geom(
        &mut self,
        conn: &Connection<'_>,
        x: i16,
        y: i16,
        width: u16,
        height: u16,
        border_width: u16,
    ) {
        if (x, y, width, height, border_width)
            == (self.x, self.y, self.width, self.height, self.border_width)
        {
            return;
        }

        self.x = x;
        self.y = y;
        self.width = width;
        self.height = height;
        self.border_width = border_width;

        conn.send_request(&ConfigureWindow {
            window: self.window,
            value_list: &[
                ConfigWindow::X(self.x.into()),
                ConfigWindow::Y(self.y.into()),
                ConfigWindow::Width(self.width.into()),
                ConfigWindow::Height(self.height.into()),
                ConfigWindow::BorderWidth(self.border_width.into()),
            ],
        });
    }

    /// Set the border colour of the X11 window to the given value (see `crate::colours::Colours`)
    /// This function does not check for success, so `conn.flush()` should be called after.
    pub fn set_border(&self, conn: &Connection<'_>, colour: u32) {
        conn.send_request(&ChangeWindowAttributes {
            window: self.window(),
            value_list: &[Cw::BorderPixel(colour)],
        });
    }

    /// Ensure this client is currently mapped / visible
    /// This function does not check for success, so `conn.flush()` should be called after.
    pub fn ensure_mapped(&mut self, conn: &Connection<'_>) {
        if !self.mapped {
            conn.send_request(&MapWindow {
                window: self.window,
            });
            self.mapped = true;
        }
    }

    /// Get the associated window
    pub const fn window(&self) -> Window {
        self.window
    }

    /// Set the event mask for this window
    /// This function does not check for success, so `conn.flush()` should be called after.
    pub fn set_event_mask(&self, conn: &Connection<'_>, event_mask: EventMask) {
        conn.send_request(&ChangeWindowAttributes {
            window: self.window(),
            value_list: &[Cw::EventMask(event_mask)],
        });
    }

    /// Sync the non-geometry related properties with EWMH hints
    /// This function does not check for success, so `conn.flush()` should be called after.
    pub fn sync_properties(&mut self, conn: &Connection<'_>, focused: bool) {
        let Some(mut hints) = hints::Xwm::get(conn, self.window) else {
            return;
        };

        if focused && hints.is_urgent() {
            hints.set_urgent(false);
            hints.apply(conn, self.window);
        } else {
            self.urgent = hints.is_urgent();
        }

        self.never_focus = hints.input().is_some_and(|i| !i);
    }

    /// Set the given window as withdrawn / not withdrawn.
    pub fn set_withdrawn(&self, conn: &Connection<'_>, withdrawn: bool) {
        conn.send_request(&ChangeProperty {
            mode: xcb::x::PropMode::Replace,
            window: self.window,
            property: conn.atoms.wm_state,
            r#type: conn.atoms.wm_state,
            data: &[u8::from(!withdrawn), 0_u8],
        });
    }

    pub fn set_urgent(&mut self, urgent: bool) {
        self.urgent = urgent;
    }

    pub const fn border_width(&self) -> u16 {
        self.border_width
    }

    pub const fn never_focus(&self) -> bool {
        self.never_focus
    }
}