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
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310

#![cfg_attr(feature = "cargo-clippy", allow(not_unsafe_ptr_arg_deref))]

use std::ffi::{CString, CStr};
use std::mem::zeroed;
use std::os::raw::c_void;
use std::slice;
use std::thread;
use std::time;
use std::time::Duration;

use x11::xlib::*;



#[macro_export]
macro_rules! with_display {
    { $display:ident => $y:expr }  => {
        {
            use std::ptr::null;
            use std::process::exit;
            use x11::xlib::{XOpenDisplay, XCloseDisplay, Display};
            use x::*;

            unsafe {
                let $display: *mut Display = XOpenDisplay(null());
                if $display.is_null() {
                    error!("Could not open display");
                    exit(1);
                }
                let result = {
                    $y
                };
                XCloseDisplay($display);
                result
            }

        }
    };
}


pub fn wait_for_kill(display: *mut Display, window: Window) {
    while window_exists(display, window) {
        thread::sleep(time::Duration::from_millis(10));
    }
}


pub fn fetch_all_windows(display: *mut Display) -> Vec<u64> {
    unsafe {
        let mut dummy: Window = zeroed();
        let root = XDefaultRootWindow(display);
        let mut p_children: *mut Window = zeroed();
        let mut n_children: u32 = 0;
        XQueryTree(display, root, &mut dummy, &mut dummy, &mut p_children, &mut n_children);

        if n_children == 0 {
            return vec![];
        }

        let children = Vec::from_raw_parts(p_children as *mut Window, n_children as usize, n_children as usize);

        XFree(p_children as *mut c_void);

        children
    }
}


pub fn set_text_property(display: *mut Display, window: Window, name: &str, value: &str) {
    unsafe {
        XChangeProperty(
            display,
            window,
            intern_atom(display, name),
            intern_atom(display, "STRING"),
            8,
            PropModeReplace,
            value.as_ptr(),
            value.len() as i32);
    }
}


pub fn get_text_property(display: *mut Display, window: Window, name: &str) -> Option<String> {
    unsafe {
        let mut actual_type: u64 = 0;
        let mut actual_format: i32 = 0;
        let mut n_items: u64 = 0;
        let mut bytes_after: u64 = 0;
        let mut prop: *mut u8 = zeroed();
        let string = intern_atom(display, "STRING");

        let name: u64 = intern_atom(display, name);
        XGetWindowProperty(
            display,
            window,
            name,
            0,
            !0,
            False,
            string,
            &mut actual_type,
            &mut actual_format,
            &mut n_items,
            &mut bytes_after,
            &mut prop);

        if prop.is_null() {
            None
        } else {
            from_cstring(prop as *mut i8)
        }
    }
}


pub fn get_window_class(display: *mut Display, window: Window) -> Option<String> {
    unsafe {
        let mut class_hint: XClassHint = zeroed();

        if XGetClassHint(display, window, &mut class_hint) == 0 {
            return None;
        }

        tap!({
            if class_hint.res_class.is_null() {
                None
            } else {
                from_cstring(class_hint.res_class)
            }
        } => {
            XFree(class_hint.res_name as *mut c_void);
            XFree(class_hint.res_class as *mut c_void);
        })
    }
}


pub fn restore_window(display: *mut Display, window: Window) {
    unsafe {
        XMapWindow(display, window);
        XRaiseWindow(display, window);
        XDeleteProperty(display, window, intern_atom(display, "_NET_WM_STATE"));
        XFlush(display);
    }
}


pub fn kill_window(display: *mut Display, window: Window) {
    unsafe {
        XKillClient(display, window);
    }
}


pub fn window_exists(display: *mut Display, window: Window) -> bool {
    unsafe {
        let mut dummy: Window = zeroed();
        let mut p_children: *mut Window = zeroed();
        let mut n_children: u32 = 0;
        let root = XDefaultRootWindow(display);

        XQueryTree(display, root, &mut dummy, &mut dummy, &mut p_children, &mut n_children);

        if n_children == 0 {
            return false
        }

        let children = slice::from_raw_parts(p_children as *const Window, n_children as usize);

        XFree(p_children as *mut c_void);

        for child in children {
            if *child == window {
                return true
            }
        }

        false
    }
}

pub fn is_window_visible(display: *mut Display, window: Window) -> bool {
    unsafe {
        let mut wattr: XWindowAttributes = zeroed();
        XGetWindowAttributes(display, window, &mut wattr);
        wattr.map_state == IsViewable
    }
}


pub fn unmap_window(display: *mut Display, window: Window) {
    unsafe {
        XUnmapWindow(display, window);
        XFlush(display);
    }
}


pub fn get_window_role(display: *mut Display, window: Window) -> Option<String> {
    get_text_property(display, window, "WM_WINDOW_ROLE")
}


pub fn set_window_role(display: *mut Display, window: Window, role: &str) {
    set_text_property(display, window, "WM_WINDOW_ROLE", role);
}


pub fn set_desktop_for_window(display: *mut Display, window: Window, desktop: i64) {
    unsafe {
        let mut wattr: XWindowAttributes = zeroed();
        XGetWindowAttributes(display, window, &mut wattr);

        let wm_desktop = intern_atom(display, "_NET_WM_DESKTOP");

        let root = XRootWindowOfScreen(wattr.screen);

        let data = {
            let mut data = ClientMessageData::new();
            data.set_long(0, desktop);
            data.set_long(1, 2);
            data
        };

        let ev = XClientMessageEvent {
            type_: ClientMessage,
            serial: 0,
            send_event: 0,
            display,
            window,
            message_type: wm_desktop,
            format: 32,
            data,
        };

        XSendEvent(
            display,
            root,
            False,
            SubstructureNotifyMask | SubstructureRedirectMask,
            &mut XEvent::from(ev));

        XFlush(display);
    }
}


pub fn get_current_desktop(display: *mut Display) ->  i64 {
    unsafe {
        let root = XDefaultRootWindow(display);

        let mut actual_type: u64 = 0;
        let mut actual_format: i32 = 0;
        let mut n_items: u64 = 0;
        let mut bytes_after: u64 = 0;
        let mut prop: *mut u8 = zeroed();

        let current_desktop: u64 = intern_atom(display, "_NET_CURRENT_DESKTOP");
        XGetWindowProperty(
            display,
            root,
            current_desktop,
            0,
            !0,
            False,
            AnyPropertyType as u64,
            &mut actual_type,
            &mut actual_format,
            &mut n_items,
            &mut bytes_after,
            &mut prop);

        if n_items > 0 {
            i64::from(*prop)
        } else {
            panic!("Fail: _NET_CURRENT_DESKTOP")
        }
    }
}


pub fn wait_for_visible(display: *mut Display, window: Window, max_tries: u64) -> bool {
    let mut tried = 0;
    while !is_window_visible(display, window) {
        tried += 1;
        thread::sleep(Duration::from_millis(1));
        if max_tries < tried {
            return false;
        }
    }
    true
}


fn intern_atom(display: *mut Display, name: &str) -> u64 {
    unsafe {
        let cstr = CString::new(name).unwrap();
        XInternAtom(display, cstr.as_ptr(), False)
    }
}


fn from_cstring(ptr: *mut i8) -> Option<String> {
    unsafe {
        CStr::from_ptr(ptr).to_str().map(|it| it.to_string()).ok()
    }
}