use crate::geometry::*; use crate::x::*; use std::sync::Arc; use x11::xrender::XRenderColor; use x11::xft::{ XftFontOpenName, XftDrawCreate, XftDrawDestroy, XftColor, XftDrawStringUtf8 }; use xcb::{ Xid }; use xcb::x::{ ClientMessageData, Gc, ChangeGc, Cw, CreateWindow, Drawable, EventMask, PolyFillArc, PolyFillRectangle, Screen, WindowClass }; mod geometry; mod x; struct DockConfiguration { thickness: u16, side: Side, background_pixel: u32, foreground_pixel: u32, foreground_render_color: XRenderColor, use_rounded_inside_corners: bool, use_rounded_outside_corners: bool, } struct Dock { configuration: DockConfiguration, window: Window, bounds: Arc>, font: *mut x11::xft::XftFont, } impl HasWindow for Dock { fn window(&self) -> &Window { &self.window } } fn main() -> xcb::Result<()> { let display = Arc::new(Display::new()?); // #d4ffcc let background_pixel = display.allocate_color(54484, 65535, 52428)?; // #5c01be let foreground_pixel = display.allocate_color(23644, 257, 48830)?; let foreground_render_color = XRenderColor { red: 23644, green: 257, blue: 48830, alpha: 65535, }; let dock = Dock::new(Arc::clone(&display), DockConfiguration { thickness: 34, side: Side::Bottom, background_pixel, foreground_pixel, foreground_render_color, use_rounded_inside_corners: false, use_rounded_outside_corners: true, })?; loop { match display.wait_for_event()? { xcb::Event::X(xcb::x::Event::Expose(_event)) => { let _ = dock.redraw(); // TODO } xcb::Event::X(xcb::x::Event::ButtonPress(event)) => { println!("click {:?}", event); } xcb::Event::X(xcb::x::Event::ClientMessage(event)) => { if let ClientMessageData::Data32([atom, ..]) = event.data() { if atom == display.atoms().wm_delete_window.resource_id() { break; } } } event => { println!("unknown event {:?}", event); } } } Ok(()) } impl Dock { fn new(display: Arc, configuration: DockConfiguration) -> xcb::Result { let screen = display.screen(); let dock_bounds = Dock::compute_bounds(&screen, &configuration); let window = Window::new(Arc::clone(&display), &mut CreateWindow { depth: xcb::x::COPY_FROM_PARENT as u8, wid: xcb::x::Window::none(), parent: screen.root(), x: dock_bounds.left as i16, y: dock_bounds.top as i16, width: dock_bounds.width, height: dock_bounds.height, border_width: 0, class: WindowClass::InputOutput, visual: screen.root_visual(), value_list: &[ Cw::BackPixel(configuration.background_pixel), Cw::EventMask(EventMask::EXPOSURE | EventMask::BUTTON_PRESS), ], })?; let xlib_display = display.connection().get_xlib_display(); let screen_index = display.screen_index(); let font = unsafe { XftFontOpenName(xlib_display, screen_index, c"FantasqueSansM Nerd Font Mono:style=Regular:size=18" .as_ptr().cast()) }; let dock = Dock { window, configuration, bounds: Arc::new(dock_bounds), font }; dock.set_string_property(&xcb::x::ATOM_WM_NAME, b"sandbar")?; dock.set_atom_property(&dock.atoms().wm_protocols, &[dock.atoms().wm_delete_window])?; dock.set_atom_property(&dock.atoms().wm_type, &[dock.atoms().wm_type_dock])?; dock.set_strut_properties(&screen)?; dock.map_window()?; Ok(dock) } fn set_strut_properties(&self, screen: &Screen) -> xcb::Result<()> { let atoms = self.atoms(); let thickness = self.configuration.thickness as u32; let strut_values = match self.configuration.side { Side::Left => &[thickness, 0 as u32, 0 as u32, 0 as u32], Side::Right => &[0 as u32, thickness, 0 as u32, 0 as u32], Side::Top => &[0 as u32, 0 as u32, thickness, 0 as u32], Side::Bottom => &[0 as u32, 0 as u32, 0 as u32, thickness], }; self.set_cardinal_property(&atoms.wm_strut, strut_values)?; let strut_partial_values = &[ strut_values[0], strut_values[1], strut_values[2], strut_values[3], 0 as u32, if self.configuration.side == Side::Left { screen.height_in_pixels() as u32 } else { 0 as u32 }, 0 as u32, if self.configuration.side == Side::Right { screen.height_in_pixels() as u32 } else { 0 as u32 }, 0 as u32, if self.configuration.side == Side::Top { screen.width_in_pixels() as u32 } else { 0 as u32 }, 0 as u32, if self.configuration.side == Side::Bottom { screen.width_in_pixels() as u32 } else { 0 as u32 }, ]; self.set_cardinal_property(&atoms.wm_strut_partial, strut_partial_values)?; Ok(()) } fn compute_bounds(screen: &Screen, configuration: &DockConfiguration) -> Rectangle { if configuration.side == Side::Top || configuration.side == Side::Bottom { let left = 0; let width = screen.width_in_pixels(); let height = configuration.thickness; let top = if configuration.side == Side::Bottom { screen.height_in_pixels() - height } else { 0 }; Rectangle { left, top, width, height } } else { let top = 0; let height = screen.height_in_pixels(); let width = configuration.thickness; let left = if configuration.side == Side::Right { screen.width_in_pixels() - width } else { 0 }; Rectangle { left, top, width, height } } } fn redraw(&self) -> xcb::Result<()> { let drawable = Drawable::Window(self.window_id()); let context = GraphicContext::new( Arc::clone(self.display()), drawable)?; if self.configuration.use_rounded_inside_corners || self.configuration.use_rounded_outside_corners { self.draw_corners(drawable, &context)?; } self.draw_text()?; Ok(()) } fn draw_corners(&self, drawable: Drawable, context: &GraphicContext) -> xcb::Result<()> { let black_pixel = self.screen().black_pixel(); let mut corner_squares = Vec::new(); let mut corner_arcs = Vec::new(); if self.configuration.use_rounded_outside_corners { for corner in self.configuration.side.corners_on_side() { let (corner_square, corner_arc) = self.compute_corner_regions(*corner); corner_squares.push(corner_square); corner_arcs.push(corner_arc); } } if self.configuration.use_rounded_inside_corners { for corner in self.configuration.side.opposite().corners_on_side() { let (corner_square, corner_arc) = self.compute_corner_regions(*corner); corner_squares.push(corner_square); corner_arcs.push(corner_arc); } } let cookie = self.send_request_checked(&ChangeGc { gc: context.graphic_context_id(), value_list: &[ Gc::Foreground(black_pixel), ], }); self.check_request(cookie)?; let cookie = self.send_request_checked(&PolyFillRectangle { drawable: drawable, gc: context.graphic_context_id(), rectangles: &corner_squares, }); self.check_request(cookie)?; let cookie = self.send_request_checked(&ChangeGc { gc: context.graphic_context_id(), value_list: &[ Gc::Foreground(self.configuration.background_pixel), ], }); self.check_request(cookie)?; let cookie = self.send_request_checked(&PolyFillArc { drawable: drawable, gc: context.graphic_context_id(), arcs: &corner_arcs, }); self.check_request(cookie)?; Ok(()) } fn compute_corner_regions(&self, corner: Corner) -> (xcb::x::Rectangle, xcb::x::Arc) { let radius = self.configuration.thickness / 2; let diameter = self.configuration.thickness; let bounds = &self.bounds; let corner_square = bounds.corner_square(corner, radius) .to_x_rectangle(); let angle1 = corner.first_clockwise_side().to_x_angle(); let corner_arc = bounds.corner_square(corner, diameter) .to_x_arc(angle1, X_CLOCKWISE_RIGHT_ANGLE); (corner_square, corner_arc) } fn draw_text(&self) -> xcb::Result<()> { let xlib_display = self.connection().get_xlib_display(); let x_visual = unsafe { x11::xlib::XDefaultVisual(xlib_display, self.display().screen_index()) }; let xft_context = unsafe { XftDrawCreate(xlib_display, self.window().window_id().resource_id() as u64, x_visual, self.screen().default_colormap().resource_id() as u64) }; let xft_color = XftColor { pixel: self.configuration.foreground_pixel as u64, color: self.configuration.foreground_render_color, }; let text = c"amazing cool fiction technically reads playful unknown telephonic works from-post"; unsafe { XftDrawStringUtf8(xft_context, &xft_color, self.font, 12, 25, text.as_ptr().cast(), text.count_bytes() as i32) }; unsafe { XftDrawDestroy(xft_context) }; Ok(()) } } impl HasDisplay for Dock { fn display(&self) -> &Arc { self.window.display() } } impl HasConnection for Dock { fn connection(&self) -> &Connection { self.window.connection() } }