summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main.rs225
-rw-r--r--src/systray.rs90
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: &gtk::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: &gtk::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(&gtk::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: &gtk::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: &gtk::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: &gtk::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());
}
}