diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 5698fbc4..cdce6481 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -22,7 +22,7 @@ jobs: package-files: | target/release/linux.zip target/debian/Weylus*.deb - pkg-cmd: cd target/release/ && zip linux.zip Weylus + pkg-cmd: cd target/release/ && zip linux.zip weylus - os: macOS-latest build-cmd: npm install -g typescript && cargo install cargo-bundle && cargo bundle --release package-files: target/release/bundle/osx/macOS.zip @@ -30,7 +30,7 @@ jobs: - os: windows-latest build-cmd: npm install -g typescript && npm run build && cargo build --release --verbose package-files: target/release/Windows.zip - pkg-cmd: cd target/release/ && 7z a Windows.zip Weylus.exe + pkg-cmd: cd target/release/ && 7z a Windows.zip weylus.exe steps: - name: Download deps diff --git a/Cargo.toml b/Cargo.toml index 4de56590..5b6c3efa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "Weylus" +name = "weylus" version = "0.1.0" authors = ["HMH "] edition = "2018" @@ -36,9 +36,7 @@ name = "Weylus" identifier = "io.github.h-m-h.weylus" [package.metadata.deb] +name = "Weylus" depends = "$auto" section = "utility" priority = "optional" -assets = [ - ["target/release/Weylus", "usr/bin/weylus", "755"], -] diff --git a/compile_flags.txt b/compile_flags.txt index 052817f1..01e2e1ea 100644 --- a/compile_flags.txt +++ b/compile_flags.txt @@ -1,2 +1,4 @@ -lX11 -lXext +-Wall +-Wextra diff --git a/lib/linux/capture.c b/lib/linux/capture.c index 7590e0d9..0aa90a97 100644 --- a/lib/linux/capture.c +++ b/lib/linux/capture.c @@ -37,8 +37,8 @@ typedef struct CaptureContext CaptureContext; struct Image { char* data; - int width; - int height; + unsigned int width; + unsigned int height; }; void* init_capture(WindowInfo* winfo, CaptureContext* ctx, Error* err) @@ -89,7 +89,9 @@ void destroy_capture(CaptureContext* ctx, Error* err) { XShmDetach(ctx->winfo.disp, &ctx->shminfo); XDestroyImage(ctx->ximg); - shmdt(ctx->shminfo.shmaddr); + if (shmdt(ctx->shminfo.shmaddr) != 0) { + fill_error(err, 1, "Failed to detach shared memory!"); + } free(ctx); } @@ -105,7 +107,7 @@ void capture_sceen(CaptureContext* ctx, struct Image* img, Error* err) ERROR(err, 1, "Failed to get window geometry!"); } // if window resized, create new capture... - if (width != ctx->ximg->width || height != ctx->ximg->height) + if (width != (unsigned int)ctx->ximg->width || height != (unsigned int)ctx->ximg->height) { XShmDetach(ctx->winfo.disp, &ctx->shminfo); XDestroyImage(ctx->ximg); diff --git a/lib/linux/uinput.c b/lib/linux/uinput.c index ca6e8a4f..f4346c41 100644 --- a/lib/linux/uinput.c +++ b/lib/linux/uinput.c @@ -35,7 +35,7 @@ void setup(int fd, const char* name, int product, Error* err) struct uinput_setup setup; memset(&setup, 0, sizeof(setup)); - strncpy(setup.name, name, UINPUT_MAX_NAME_SIZE); + strncpy(setup.name, name, UINPUT_MAX_NAME_SIZE - 1); setup.id.bustype = BUS_VIRTUAL; setup.id.vendor = 0x1; setup.id.product = product; @@ -114,7 +114,6 @@ void init_multitouch(int fd, Error* err) if (ioctl(fd, UI_SET_KEYBIT, BTN_TOOL_QUINTTAP) < 0) ERROR(err, 1, "error: ioctl UI_SET_KEYBIT"); - if (ioctl(fd, UI_SET_EVBIT, EV_ABS) < 0) ERROR(err, 1, "error: ioctl UI_SET_EVBIT EV_ABS"); diff --git a/lib/linux/xwindows.c b/lib/linux/xwindows.c index d4cdac63..20442346 100644 --- a/lib/linux/xwindows.c +++ b/lib/linux/xwindows.c @@ -9,8 +9,7 @@ int locale_to_utf8(char* src, char* dest, size_t size) { iconv_t icd = iconv_open("UTF-8//IGNORE", ""); - size_t src_size = strlen(src); - size_t inbytes = src_size; + size_t src_size = size; size_t outbytes_left = MAX_PROPERTY_VALUE_LEN - 1; int ret = iconv(icd, &src, &src_size, &dest, &outbytes_left); iconv_close(icd); @@ -160,7 +159,6 @@ size_t get_window_info(Display* disp, WindowInfo* windows, size_t size, Error* e { Window* client_list; unsigned long client_list_size; - int max_client_machine_len = 0; if ((client_list = get_client_list(disp, &client_list_size, err)) == NULL) { @@ -170,13 +168,13 @@ size_t get_window_info(Display* disp, WindowInfo* windows, size_t size, Error* e size_t num_windows = client_list_size / sizeof(Window); /* print the list */ - for (int i = 0; i < num_windows && i < size; i++) + for (size_t i = 0; i < num_windows && i < size; i++) { char* title_utf8 = get_window_title(disp, client_list[i], NULL); if (title_utf8 == NULL) { - title_utf8 = malloc(16); - snprintf(title_utf8, 16, "UNKNOWN %d", i); + title_utf8 = malloc(32); + snprintf(title_utf8, 32, "UNKNOWN %lu", i); } unsigned long* desktop; @@ -192,7 +190,7 @@ size_t get_window_info(Display* disp, WindowInfo* windows, size_t size, Error* e windows[i].win = client_list[i]; // special desktop ID -1 means "all desktops" // use -2 to indicate that no desktop has been found - windows[i].desktop_id = desktop ? *desktop : -2; + windows[i].desktop_id = desktop ? (signed long)*desktop : -2; strncpy(windows[i].title, title_utf8, sizeof(windows->title)); free(title_utf8); free(desktop); @@ -219,8 +217,6 @@ void get_window_geometry_relative( WindowInfo* winfo, float* x, float* y, float* width, float* height, Error* err) { Window junkroot; - int junkx, junky; - unsigned int bw, depth; int x_tmp, y_tmp; XWindowAttributes window_attributes; @@ -232,8 +228,8 @@ void get_window_geometry_relative( winfo->disp, winfo->win, window_attributes.root, - junkx, - junky, + window_attributes.x, + window_attributes.y, &x_tmp, &y_tmp, &junkroot)) @@ -247,15 +243,11 @@ void get_window_geometry_relative( *height = window_attributes.height / (float)window_attributes.screen->height; } -void get_root_window_info(Display* disp, WindowInfo* winfo, Error* err) +void get_root_window_info(Display* disp, WindowInfo* winfo) { Window root = DefaultRootWindow(disp); - char* title_utf8 = get_window_title(disp, root, NULL); - if (title_utf8 == NULL) - { - title_utf8 = malloc(13); - snprintf(title_utf8, 13, "UNKNOWN Root"); - } + char* title_utf8 = malloc(12); + snprintf(title_utf8, 12, "Root Window"); winfo->disp = disp; strncpy(winfo->title, title_utf8, sizeof(winfo->title)); winfo->win = root; @@ -325,7 +317,6 @@ void activate_window(WindowInfo* winfo, Error* err) ERROR(err, 1, "Cannot find desktop ID of the window."); } } - XEvent event; client_msg(winfo->disp, DefaultRootWindow(winfo->disp), "_NET_CURRENT_DESKTOP", *desktop, 0, 0, 0, 0, err); free(desktop); OK_OR_ABORT(err); diff --git a/src/gui.rs b/src/gui.rs index 827e0e12..438422b7 100644 --- a/src/gui.rs +++ b/src/gui.rs @@ -122,6 +122,7 @@ pub fn run() { .below_of(&check_faster_screencapture, padding) .with_label("Capturing windows is\nonly supported on Linux!"); + #[allow(unused_mut)] let mut choice_window = Choice::default() .with_size(width, height) .below_of(&label_window_choice, 0); @@ -210,7 +211,7 @@ pub fn run() { #[cfg(target_os = "linux")] let mut x11_context = X11Context::new().unwrap(); #[cfg(target_os = "linux")] - let capture_window = Rc::new(RefCell::new(x11_context.root_window().unwrap())); + let capture_window = Rc::new(RefCell::new(x11_context.root_window())); #[cfg(target_os = "linux")] { @@ -222,7 +223,7 @@ pub fn run() { let mut choice_window = choice_window_ref.borrow_mut(); choice_window.clear(); { - let root_window = x11_context.root_window().unwrap(); + let root_window = x11_context.root_window(); let capture_window = capture_window.clone(); choice_window.add( "", diff --git a/src/input/uinput_device.rs b/src/input/uinput_device.rs index fc3b3569..4a0f4373 100644 --- a/src/input/uinput_device.rs +++ b/src/input/uinput_device.rs @@ -8,23 +8,23 @@ use crate::x11helper::WindowInfo; use crate::cerror::CError; -use tracing::{info, warn}; +use tracing::warn; extern "C" { fn init_uinput_pointer(err: *mut CError) -> c_int; - fn init_uinput_multitouch(err: *mut CError) -> c_int; +// fn init_uinput_multitouch(err: *mut CError) -> c_int; fn destroy_uinput_device(fd: c_int); fn send_uinput_event(device: c_int, typ: c_int, code: c_int, value: c_int, err: *mut CError); } -struct MultiTouch { +/*struct MultiTouch { id: i64, -} +}*/ pub struct GraphicTablet { pointer_fd: c_int, - multitouch_fd: c_int, - multi_touches: [Option; 5], +// multitouch_fd: c_int, +// multi_touches: [Option; 5], winfo: WindowInfo, x: f64, y: f64, @@ -45,8 +45,8 @@ impl GraphicTablet { }*/ let tblt = Self { pointer_fd: pointer_fd, - multitouch_fd: 0, // multitouch_fd, - multi_touches: Default::default(), +// multitouch_fd: 0, // multitouch_fd, +// multi_touches: Default::default(), winfo: winfo, x: 0.0, y: 0.0, @@ -70,7 +70,7 @@ impl GraphicTablet { (p * 65535.0) as i32 } - fn transform_touch_size(&self, s: f64) -> i32 { + /*fn transform_touch_size(&self, s: f64) -> i32 { (s * 65535.0) as i32 } @@ -88,7 +88,7 @@ impl GraphicTablet { } _ => None, }) - } + }*/ fn send(&self, typ: c_int, code: c_int, value: c_int) { let mut err = CError::new(); @@ -100,7 +100,7 @@ impl GraphicTablet { } } - fn send_touch(&self, typ: c_int, code: c_int, value: c_int) { + /*fn send_touch(&self, typ: c_int, code: c_int, value: c_int) { let mut err = CError::new(); unsafe { send_uinput_event(self.multitouch_fd, typ, code, value, &mut err); @@ -108,7 +108,7 @@ impl GraphicTablet { if err.is_err() { warn!("{}", err); } - } + }*/ } impl Drop for GraphicTablet { @@ -123,32 +123,32 @@ impl Drop for GraphicTablet { // Event Types const ET_SYNC: c_int = 0x00; const ET_KEY: c_int = 0x01; -const ET_RELATIVE: c_int = 0x02; +//const ET_RELATIVE: c_int = 0x02; const ET_ABSOLUTE: c_int = 0x03; // Event Codes const EC_SYNC_REPORT: c_int = 1; -const EC_SYNC_MT_REPORT: c_int = 2; +//const EC_SYNC_MT_REPORT: c_int = 2; const EC_KEY_MOUSE_LEFT: c_int = 0x110; const EC_KEY_TOOL_PEN: c_int = 0x140; -const EC_KEY_TOUCH: c_int = 0x14a; -const EC_KEY_STYLUS: c_int = 0x14b; -const EC_KEY_TOOL_FINGER: c_int = 0x145; -const EC_KEY_TOOL_DOUBLETAP: c_int = 0x14d; -const EC_KEY_TOOL_TRIPLETAP: c_int = 0x14e; -const EC_KEY_TOOL_QUADTAP: c_int = 0x14f; /* Four fingers on trackpad */ -const EC_KEY_TOOL_QUINTTAP: c_int = 0x148; /* Five fingers on trackpad */ +//const EC_KEY_TOUCH: c_int = 0x14a; +//const EC_KEY_STYLUS: c_int = 0x14b; +//const EC_KEY_TOOL_FINGER: c_int = 0x145; +//const EC_KEY_TOOL_DOUBLETAP: c_int = 0x14d; +//const EC_KEY_TOOL_TRIPLETAP: c_int = 0x14e; +//const EC_KEY_TOOL_QUADTAP: c_int = 0x14f; /* Four fingers on trackpad */ +//const EC_KEY_TOOL_QUINTTAP: c_int = 0x148; /* Five fingers on trackpad */ -const EC_RELATIVE_X: c_int = 0x00; -const EC_RELATIVE_Y: c_int = 0x01; +//const EC_RELATIVE_X: c_int = 0x00; +//const EC_RELATIVE_Y: c_int = 0x01; const EC_ABSOLUTE_X: c_int = 0x00; const EC_ABSOLUTE_Y: c_int = 0x01; const EC_ABSOLUTE_PRESSURE: c_int = 0x18; const EC_ABSOLUTE_TILT_X: c_int = 0x1a; const EC_ABSOLUTE_TILT_Y: c_int = 0x1b; -const EC_ABS_MT_SLOT: c_int = 0x2f; /* MT slot being modified */ +/*const EC_ABS_MT_SLOT: c_int = 0x2f; /* MT slot being modified */ const EC_ABS_MT_TOUCH_MAJOR: c_int = 0x30; /* Major axis of touching ellipse */ const EC_ABS_MT_TOUCH_MINOR: c_int = 0x31; /* Minor axis (omit if circular) */ const EC_ABS_MT_ORIENTATION: c_int = 0x34; /* Ellipse orientation */ @@ -158,7 +158,7 @@ const EC_ABS_MT_TRACKING_ID: c_int = 0x39; /* Unique ID of initiated contact */ const EC_ABS_MT_PRESSURE: c_int = 0x3a; /* Pressure on contact area */ // Maximum for Absolute Values -const ABS_MAX: c_int = 65535; +const ABS_MAX: c_int = 65535;*/ impl PointerDevice for GraphicTablet { fn send_event(&mut self, event: &PointerEvent) { @@ -290,7 +290,7 @@ impl PointerDevice for GraphicTablet { self.send( ET_ABSOLUTE, EC_ABSOLUTE_PRESSURE, - self.transform_pressure(1.0), + self.transform_pressure(0.5), ); match event.event_type { PointerEventType::DOWN => self.send(ET_KEY, EC_KEY_MOUSE_LEFT, 1), diff --git a/src/main.rs b/src/main.rs index f2289c0f..77893155 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,8 +20,13 @@ mod websocket; mod x11helper; fn main() { - let subscriber = tracing_subscriber::fmt() + #[cfg(debug_assertions)] + let _subscriber = tracing_subscriber::fmt() .with_max_level(Level::TRACE) .init(); + #[cfg(not(debug_assertions))] + let _subscriber = tracing_subscriber::fmt() + .with_max_level(Level::WARN) + .init(); gui::run(); } diff --git a/src/screen_capture/linux.rs b/src/screen_capture/linux.rs index 173a9f8e..3edcf038 100644 --- a/src/screen_capture/linux.rs +++ b/src/screen_capture/linux.rs @@ -1,4 +1,4 @@ -use std::os::raw::{c_int, c_void}; +use std::os::raw::{c_uint, c_void}; use std::slice::from_raw_parts; use crate::cerror::CError; @@ -14,8 +14,8 @@ extern "C" { #[repr(C)] struct CImage { data: *const u8, - width: c_int, - height: c_int, + width: c_uint, + height: c_uint, } impl CImage { @@ -38,7 +38,6 @@ impl CImage { pub struct ScreenCaptureX11 { handle: *mut c_void, - rgb_buf: Vec, png_buf: Vec, } @@ -53,32 +52,11 @@ impl ScreenCaptureX11 { } else { return Ok(Self { handle: handle, - rgb_buf: Vec::::new(), png_buf: Vec::::new(), }); } } - fn convert_to_rgb(&mut self, img: &CImage) { - let buf_size = (img.height * img.width * 3) as usize; - if buf_size > self.rgb_buf.len() { - self.rgb_buf = vec![0; buf_size]; - } - let data = img.data(); - self.rgb_buf[0..buf_size] - .iter_mut() - .enumerate() - .for_each(|(i, byte)| { - let j = i / 3 * 4; - match i % 3 { - 0 => *byte = data[j + 2], - 1 => *byte = data[j + 1], - 2 => *byte = data[j], - _ => (), - } - }); - } - fn convert_to_png(&mut self, img: &CImage) -> Result<&[u8], std::io::Error> { let mut header = mtpng::Header::new(); header.set_size(img.width as u32, img.height as u32)?; diff --git a/src/stream_handler.rs b/src/stream_handler.rs index 29472f51..8fcdc26e 100644 --- a/src/stream_handler.rs +++ b/src/stream_handler.rs @@ -3,7 +3,7 @@ use std::time::{Duration, Instant}; use websocket::Message; use websocket::OwnedMessage; -use tracing::warn; +use tracing::{trace, warn}; use crate::input::pointer::PointerDevice; use crate::protocol::NetMessage; @@ -29,7 +29,7 @@ impl StreamHandler for PointerStreamHandler { fn process(&mut self, _: WsWriter, message: &OwnedMessage) { match message { OwnedMessage::Text(s) => { - println!("{}", &s); + trace!("Pointerevent: {}", &s); let message: Result = serde_json::from_str(&s); match message { Ok(message) => match message { diff --git a/src/web.rs b/src/web.rs index e2492c13..a0178d78 100644 --- a/src/web.rs +++ b/src/web.rs @@ -5,8 +5,10 @@ use serde::Serialize; use std::collections::HashMap; use std::net::SocketAddr; use std::sync::mpsc; +use std::sync::mpsc::SendError; use std::sync::Arc; use tokio::sync::mpsc as mpsc_tokio; +use tracing::warn; #[derive(Serialize)] struct WebConfig { @@ -48,7 +50,7 @@ async fn serve<'a>( if let Some(pass) = params.get("password") { if pass == password { authed = true; - sender.send(Web2GuiMessage::Info(format!("User authed."))); + log_gui_send_error(sender.send(Web2GuiMessage::Info(format!("User authed.")))); } } } @@ -100,6 +102,12 @@ pub enum Web2GuiMessage { Shutdown, } +fn log_gui_send_error(res: Result<(), SendError>) { + if let Err(err) = res { + warn!("Webserver: Failed to send message to gui: {}", err); + } +} + struct Context<'a> { bind_addr: SocketAddr, ws_pointer_port: u16, @@ -166,16 +174,16 @@ async fn run_server( } } }); - sender2.send(Web2GuiMessage::Info(format!( + log_gui_send_error(sender2.send(Web2GuiMessage::Info(format!( "Webserver listening at {}...", addr - ))); + )))); match server.await { - Ok(_) => sender2.send(Web2GuiMessage::Info("Webserver shutdown!".into())), - Err(err) => sender2.send(Web2GuiMessage::Error(format!( + Ok(_) => log_gui_send_error(sender2.send(Web2GuiMessage::Info("Webserver shutdown!".into()))), + Err(err) => log_gui_send_error(sender2.send(Web2GuiMessage::Error(format!( "Webserver exited error: {}", err - ))), + )))), }; - sender2.send(Web2GuiMessage::Shutdown); + log_gui_send_error(sender2.send(Web2GuiMessage::Shutdown)); } diff --git a/src/websocket.rs b/src/websocket.rs index 73fb8b88..ab969188 100644 --- a/src/websocket.rs +++ b/src/websocket.rs @@ -4,9 +4,10 @@ use std::sync::{ atomic::{AtomicBool, Ordering}, mpsc, Arc, Mutex, }; +use std::sync::mpsc::SendError; use std::thread::spawn; use std::time::Duration; -use tracing::{info, warn}; +use tracing::warn; use websocket::sender::Writer; use websocket::sync::Server; @@ -33,6 +34,12 @@ pub enum Gui2WsMessage { Shutdown, } +fn log_gui_send_error(res: Result<(), SendError>) { + if let Err(err) = res { + warn!("Websocket: Failed to send message to gui: {}", err); + } +} + #[cfg(target_os = "linux")] pub fn run( sender: mpsc::Sender, @@ -64,10 +71,10 @@ pub fn run( for client in clients.values() { let client = client.lock().unwrap(); if let Err(err) = client.shutdown_all() { - sender.send(Ws2GuiMessage::Error(format!( + log_gui_send_error(sender.send(Ws2GuiMessage::Error(format!( "Could not shutdown websocket: {}", err - ))); + )))); } } shutdown.store(true, Ordering::Relaxed); @@ -250,28 +257,28 @@ fn listen_websocket( { let server = Server::bind(addr); if let Err(err) = server { - sender.send(Ws2GuiMessage::Error(format!( + log_gui_send_error(sender.send(Ws2GuiMessage::Error(format!( "Failed binding to socket: {}", err - ))); + )))); return; } let mut server = server.unwrap(); if let Err(err) = server.set_nonblocking(true) { - sender.send(Ws2GuiMessage::Warning(format!( + log_gui_send_error(sender.send(Ws2GuiMessage::Warning(format!( "Could not set websocket to non-blocking, graceful shutdown may be impossible now: {}", err - ))); + )))); } loop { std::thread::sleep(std::time::Duration::from_millis(10)); let sender = sender.clone(); if shutdown.load(Ordering::Relaxed) { - sender.send(Ws2GuiMessage::Info(format!( + log_gui_send_error(sender.send(Ws2GuiMessage::Info(format!( "Shutting down websocket: {}", addr - ))); + )))); return; } let clients = clients.clone(); @@ -282,10 +289,10 @@ fn listen_websocket( spawn(move || { let client = request.accept(); if let Err((_, err)) = client { - sender.send(Ws2GuiMessage::Warning(format!( + log_gui_send_error(sender.send(Ws2GuiMessage::Warning(format!( "Failed to accept client: {}", err - ))); + )))); return; } let client = client.unwrap(); @@ -294,19 +301,19 @@ fn listen_websocket( } let peer_addr = client.peer_addr(); if let Err(err) = peer_addr { - sender.send(Ws2GuiMessage::Warning(format!( + log_gui_send_error(sender.send(Ws2GuiMessage::Warning(format!( "Failed to retrieve client address: {}", err - ))); + )))); return; } let peer_addr = peer_addr.unwrap(); let client = client.split(); if let Err(err) = client { - sender.send(Ws2GuiMessage::Warning(format!( + log_gui_send_error(sender.send(Ws2GuiMessage::Warning(format!( "Failed to setup connection: {}", err - ))); + )))); return; } let (mut ws_receiver, ws_sender) = client.unwrap(); @@ -320,10 +327,10 @@ fn listen_websocket( let stream_handler = create_stream_handler(); if let Err(err) = stream_handler { - sender.send(Ws2GuiMessage::Error(format!( + log_gui_send_error(sender.send(Ws2GuiMessage::Error(format!( "Failed to create stream handler: {}", err - ))); + )))); return; } diff --git a/src/x11helper.rs b/src/x11helper.rs index ce94c38c..c6535f49 100644 --- a/src/x11helper.rs +++ b/src/x11helper.rs @@ -1,13 +1,13 @@ use std::ffi::CStr; use std::fmt; -use std::os::raw::{c_char, c_float, c_int, c_uint, c_void}; +use std::os::raw::{c_char, c_float, c_int, c_void}; use crate::cerror::CError; extern "C" { fn get_window_info(disp: *mut c_void, windows: *mut WindowInfo, err: *mut CError) -> isize; - fn get_root_window_info(disp: *mut c_void, root: *mut WindowInfo, err: *mut CError); + fn get_root_window_info(disp: *mut c_void, root: *mut WindowInfo); fn get_window_geometry_relative( winfo: *const WindowInfo, @@ -106,18 +106,14 @@ impl X11Context { )) } - pub fn root_window(&mut self) -> Result { - let mut err = CError::new(); + pub fn root_window(&mut self) -> WindowInfo { let mut root_window_info = WindowInfo::new(); fltk::app::lock().unwrap(); unsafe { - get_root_window_info(self.disp, &mut root_window_info, &mut err); + get_root_window_info(self.disp, &mut root_window_info); } fltk::app::unlock(); - if err.is_err() { - return Err(err); - } - Ok(root_window_info) + root_window_info } }