diff options
-rw-r--r-- | src/main.rs | 225 | ||||
-rw-r--r-- | src/systray.rs | 90 |
2 files changed, 299 insertions, 16 deletions
diff --git a/src/main.rs b/src/main.rs index 56c8629..4be5992 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +use gdk::prelude::*; use gio::prelude::*; use gtk::prelude::*; @@ -9,25 +10,183 @@ use std::rc::Rc; mod systray; +const STYLE: &str = r#" +#bar { + background-color: #222; +} + +#desktops { + color: #eee; +} + +.tray-client { + background-color: #fff; +} +"#; + +struct Desktops { + display: *mut x11::xlib::Display, + root: x11::xlib::Window, + label: glib::WeakRef<gtk::Label>, + cardinal: x11::xlib::Atom, + string: x11::xlib::Atom, + curdesk: x11::xlib::Atom, + desknames: x11::xlib::Atom, + deskcount: x11::xlib::Atom, +} + +use std::os::raw::{c_long, c_ulong}; + +impl Desktops { + fn new(label: >k::Label) -> Self { + let display = unsafe { x11::xlib::XOpenDisplay(std::ptr::null()) }; + Self { + label: label.downgrade(), + display, + root: unsafe { x11::xlib::XRootWindow(display, 0) }, + cardinal: systray::intern_xatom("CARDINAL", display), + string: systray::intern_xatom("UTF8_STRING", display), + curdesk: systray::intern_xatom("_NET_CURRENT_DESKTOP", display), + desknames: systray::intern_xatom("_NET_DESKTOP_NAMES", display), + deskcount: systray::intern_xatom("_NET_NUMBER_OF_DESKTOPS", display), + } + } + + fn get_number_property(&self, prop: x11::xlib::Atom) -> usize { + unsafe { + let mut ret_type: x11::xlib::Atom = 0; + let mut format = 0; + let mut nitems: c_ulong = 0; + let mut bytes_after: c_ulong = 0; + let mut retval: *mut u8 = std::ptr::null_mut(); + x11::xlib::XGetWindowProperty( + self.display, + self.root, + prop, + 0, + 1, + 0, + self.cardinal, + &mut ret_type as *mut _, + &mut format as *mut _, + &mut nitems as *mut _, + &mut bytes_after as *mut _, + &mut retval as *mut _, + ); + assert_eq!(ret_type, self.cardinal); + let value = *(retval as *mut std::os::raw::c_long); + x11::xlib::XFree(retval as *mut _); + value as usize + } + } + + fn get_string_property(&self, prop: x11::xlib::Atom) -> String { + unsafe { + let mut ret_type: x11::xlib::Atom = 0; + let mut format = 0; + let mut nitems: c_ulong = 0; + let mut bytes_after: c_ulong = 0; + let mut retval: *mut u8 = std::ptr::null_mut(); + x11::xlib::XGetWindowProperty( + self.display, + self.root, + prop, + 0, + 100, + 0, + self.string, + &mut ret_type as *mut _, + &mut format as *mut _, + &mut nitems as *mut _, + &mut bytes_after as *mut _, + &mut retval as *mut _, + ); + let s = std::slice::from_raw_parts(retval, nitems as usize); + let s = std::str::from_utf8_unchecked(s); + let s = String::from(s); + x11::xlib::XFree(retval as *mut _); + s + } + } + + fn get_desktops(&self) -> Vec<String> { + let ndesktops = self.get_number_property(self.deskcount); + let desknames = self.get_string_property(self.desknames); + desknames + .split('\0') + .take(ndesktops) + .map(String::from) + .collect() + } + + fn update_label(&self) { + let desktops = self.get_desktops(); + let curdesk = self.get_number_property(self.curdesk); + let desklabel: String = desktops + .into_iter() + .enumerate() + .map(|(i, s)| { + if i == curdesk { + format!("[{}] ", s) + } else { + format!("{} ", s) + } + }) + .collect(); + if let Some(label) = self.label.upgrade() { + label.set_text(&desklabel); + } + } +} + +impl Drop for Desktops { + fn drop(&mut self) { + let p = std::mem::replace(&mut self.display, std::ptr::null_mut()); + if p != std::ptr::null_mut() { + unsafe { x11::xlib::XCloseDisplay(p) }; + } + } +} + +unsafe extern "C" fn property_change_filter( + _xevent: *mut std::ffi::c_void, + _gdkevent: *mut gdk_sys::GdkEvent, + _pw: *mut std::ffi::c_void, +) -> i32 { + let atoms: &Desktops = &(*(_pw as *mut Desktops)); + let xevent: &mut x11::xlib::XEvent = &mut (*(_xevent as *mut x11::xlib::XEvent)); + if xevent.type_ == x11::xlib::PropertyNotify { + let pev = &xevent.property; + if pev.atom == atoms.curdesk || pev.atom == atoms.desknames { + atoms.update_label(); + } + } + gdk_sys::GDK_FILTER_CONTINUE +} + fn build_ui(application: >k::Application) { let window = gtk::ApplicationWindow::new(application); - - window.set_title("First GTK+ Program"); - window.set_border_width(10); - window.set_position(gtk::WindowPosition::Center); - window.set_default_size(350, 70); + window.set_widget_name("bar"); + window.set_title("My bar"); + window.set_decorated(false); + window.set_type_hint(gdk::WindowTypeHint::Dock); + let width = window.get_screen().expect("No screen?").get_width(); + window.move_(0, 0); + window.resize(width, 24); let disp = gdk::DisplayManager::get() .get_default_display() .expect("Strange"); let screen = disp.get_default_screen(); - let vbox = gtk::Box::new(gtk::Orientation::Vertical, 2); - window.add(&vbox); + let hbox = gtk::Box::new(gtk::Orientation::Horizontal, 2); + window.add(&hbox); let tray = Rc::new(RefCell::new(systray::Systray::manage_screen(&screen))); - vbox.add(tray.borrow().get_tray_widget()); - let label = gtk::Label::new(Some("Nothing to see here")); - vbox.add(&label); + let desktop_label = gtk::Label::new(Some("Desktops")); + desktop_label.set_widget_name("desktops"); + hbox.pack_start(&desktop_label, false, false, 0); + hbox.pack_start(>k::Label::new(None), true, true, 0); + hbox.pack_end(tray.borrow().get_tray_widget(), false, false, 0); gtk::timeout_add( 1000, clone!(@weak tray => @default-return glib::Continue(false), move || { @@ -41,6 +200,40 @@ fn build_ui(application: >k::Application) { }); window.show_all(); + let topw = window + .get_toplevel() + .expect("top level must be possible") + .get_window() + .expect("Window must be there"); + let cardinal = gdk::Atom::intern("CARDINAL"); + let strut = gdk::Atom::intern("_NET_WM_STRUT"); + let partial = gdk::Atom::intern("_NET_WM_STRUT_PARTIAL"); + let data = gdk::ChangeData::ULongs(&[0, 0, 24, 0]); + gdk::property_change(&topw, &strut, &cardinal, 32, gdk::PropMode::Replace, data); + let partials = [0, 0, 24, 0, 0, 0, 0, 0, 0, (width - 1) as u64, 0, 0]; + let data = gdk::ChangeData::ULongs(&partials); + gdk::property_change(&topw, &partial, &cardinal, 32, gdk::PropMode::Replace, data); + + // Now try to register for changes on the root window + let win = window + .get_screen() + .expect("no screen") + .get_root_window() + .expect("no root"); + let events = win.get_events(); + win.set_events(events | gdk::EventMask::PROPERTY_CHANGE_MASK); + let disp = unsafe { x11::xlib::XOpenDisplay(std::ptr::null()) }; + let desktops = Desktops::new(&desktop_label); + let atoms = Box::into_raw(Box::new(desktops)); + unsafe { x11::xlib::XCloseDisplay(disp) }; + unsafe { + use glib::translate::ToGlibPtr; + gdk_sys::gdk_window_add_filter( + win.to_glib_none().0, + Some(property_change_filter), + atoms as *mut _, + ); + } } fn main() { @@ -48,6 +241,18 @@ fn main() { .expect("Initialization failed..."); application.connect_activate(|app| { + // The CSS "magic" happens here. + let provider = gtk::CssProvider::new(); + provider + .load_from_data(STYLE.as_bytes()) + .expect("Failed to load CSS"); + // We give the CssProvided to the default screen so the CSS rules we added + // can be applied to our window. + gtk::StyleContext::add_provider_for_screen( + &gdk::Screen::get_default().expect("Error initializing gtk css provider."), + &provider, + gtk::STYLE_PROVIDER_PRIORITY_APPLICATION, + ); build_ui(app); }); diff --git a/src/systray.rs b/src/systray.rs index 3370c73..b06f08d 100644 --- a/src/systray.rs +++ b/src/systray.rs @@ -20,7 +20,7 @@ struct SystrayAtoms { manager: x11::xlib::Atom, } -fn intern_xatom(name: &str, disp: *mut x11::xlib::Display) -> x11::xlib::Atom { +pub fn intern_xatom(name: &str, disp: *mut x11::xlib::Display) -> x11::xlib::Atom { gdk::error_trap_push(); let atom = unsafe { use std::ffi::CString; @@ -171,8 +171,22 @@ impl SystrayInner { } fn create_dock(&self, xid: gtk::xlib::Window) { + let mut attrs: x11::xlib::XSetWindowAttributes = + unsafe { std::mem::MaybeUninit::zeroed().assume_init() }; + attrs.background_pixmap = x11::xlib::ParentRelative as x11::xlib::Pixmap; + attrs.event_mask = x11::xlib::PropertyChangeMask | x11::xlib::StructureNotifyMask; + unsafe { + x11::xlib::XChangeWindowAttributes( + self.display.unwrap(), + xid as x11::xlib::Window, + x11::xlib::CWBackPixmap | x11::xlib::CWEventMask, + &mut attrs as *mut _, + ); + x11::xlib::XAddToSaveSet(self.display.unwrap(), xid as x11::xlib::Window); + } let client = client::TrayClient::new(self.display.unwrap(), xid); self.tray_widget.pack_start(&client, false, false, 0); + self.tray_widget.show_all(); client.begin_embed() } @@ -295,7 +309,7 @@ mod client { .get() .expect("Conformity expected in Object::set_property") .expect("Expected window to be provided"); - println!("Replacing socket with {:?}", window); + println!("Replacing window ID with 0x{:X}", window); self.window.replace(window); } _ => unimplemented!(), @@ -314,6 +328,8 @@ mod client { self.parent_constructed(obj); let self_ = obj.downcast_ref::<TrayClient>().expect("Not a TrayClient"); self_.add(&self.socket); + let styles = self_.get_style_context(); + styles.add_class("tray-client"); } } @@ -342,16 +358,45 @@ mod client { } self.parent_size_allocate(widget, allocation); if (moved || resized) && widget.get_mapped() { - // TODO: Send an expose event to the mapped client somehow? + self.force_redraw(); + } + } + + fn realize(&self, widget: >k::Widget) { + self.parent_realize(widget); + widget.show_all(); + let sock_window = self.socket.get_window().expect("No window?"); + let bin_window = sock_window.get_parent().expect("No parent?"); + println!( + "{:?} == {:?}", + sock_window.get_visual(), + bin_window.get_visual() + ); + if *self.has_alpha.borrow() { + todo!("Write the has_alpha stuff") + } else if sock_window.get_visual() == bin_window.get_visual() { + // We're doing parent-relative backgroundery + println!("Parent relative background mode"); + self.parent_relative.replace(true); + sock_window.set_background_pattern(None); + } else { + println!("Ugly-ass mode"); } + sock_window.set_composited(*self.has_alpha.borrow()); + self.socket + .set_app_paintable(*self.has_alpha.borrow() || *self.parent_relative.borrow()); + //self.socket.set_double_buffered(!*self.parent_relative.borrow()); } fn draw(&self, widget: >k::Widget, context: &cairo::Context) -> glib::signal::Inhibit { + self.parent_draw(widget, context); if *self.has_alpha.borrow() { + println!("Alpha render"); context.set_source_rgba(0.0, 0.0, 0.0, 0.0); context.set_operator(cairo::Operator::Source); context.paint(); - } else { + } else if *self.parent_relative.borrow() { + println!("Render, parent relative"); if let Some(clip) = context.get_clip_rectangle() { let target = context.get_group_target(); target.flush(); @@ -400,6 +445,8 @@ mod client { ) } } + } else { + println!("Dodgy ass mess"); } glib::signal::Inhibit(false) } @@ -482,6 +529,38 @@ mod client { ); println!("Has alpha: {}", self.has_alpha.borrow()); } + + fn force_redraw(&self) { + if self.socket.get_mapped() && *self.parent_relative.borrow() { + println!("Mapped and parent relative, force redraw"); + let alloc = self.socket.get_allocation(); + let xeev = x11::xlib::XExposeEvent { + type_: x11::xlib::Expose, + window: *self.window.borrow() as u64, + x: 0, + y: 0, + width: alloc.width, + height: alloc.height, + count: 0, + // Spare bits + serial: 0, + display: std::ptr::null_mut(), + send_event: 0, + }; + gdk::error_trap_push(); + unsafe { + let mut event: x11::xlib::XEvent = xeev.into(); + x11::xlib::XSendEvent( + *self.display.borrow(), + *self.window.borrow() as u64, + 0, + x11::xlib::ExposureMask, + &mut event as *mut _, + ); + } + gdk::error_trap_pop_ignored(); + } + } } glib_wrapper! { @@ -505,13 +584,12 @@ mod client { .expect("TrayClient is not of expected type"); let priv_ = TrayClientPrivate::from_instance(&ret); priv_.display.replace(display); + priv_.learn_about_window(); ret } pub fn begin_embed(&self) { let priv_ = TrayClientPrivate::from_instance(self); - priv_.learn_about_window(); - self.show_all(); priv_.socket.add_id(*priv_.window.borrow()); } } |