diff --git a/userspace/colors/src/bin/colors-bar.rs b/userspace/colors/src/bin/colors-bar.rs index 98a7549b..61e9aaf7 100644 --- a/userspace/colors/src/bin/colors-bar.rs +++ b/userspace/colors/src/bin/colors-bar.rs @@ -1,23 +1,48 @@ #![feature(yggdrasil_os, rustc_private)] -use std::{os::fd::AsRawFd, process::ExitCode, sync::Arc, time::Duration}; +use std::{ + os::fd::AsRawFd, + process::ExitCode, + sync::{ + atomic::{AtomicU32, AtomicUsize, Ordering}, + Arc, + }, + time::Duration, +}; use chrono::{DateTime, Timelike}; use cross::io::{Poll, TimerFd}; use libcolors::{ application::{window::Window, Application}, error::Error, + event::WindowManagementEvent, message::{CreateWindowInfo, WindowType}, }; use libpsf::PcScreenFont; use runtime::rt::time::get_real_time; -fn draw_string(dt: &mut [u32], font: &PcScreenFont, x: u32, y: u32, text: &str, stride: usize) { +static WORKSPACE_ACTIVITY: AtomicU32 = AtomicU32::new(0); +static WORKSPACE_MAX: AtomicUsize = AtomicUsize::new(1); +static CURRENT_WORKSPACE: AtomicUsize = AtomicUsize::new(0); + +const WHITE: u32 = 0xFFFFFFFF; +const BLACK: u32 = 0xFF000000; + +fn draw_string( + dt: &mut [u32], + font: &PcScreenFont, + x: u32, + y: u32, + text: &str, + stride: usize, + fg: u32, + bg: u32, +) { let y = y as usize; for (i, ch) in text.chars().enumerate() { let x = x as usize + i * font.width() as usize; font.map_glyph_pixels(ch as u32, |px, py, set| { - let color = if set { 0xFFFFFFFF } else { 0xFF000000 }; + let color = if set { fg } else { bg }; dt[(y + py) * stride + x + px] = color; }); } @@ -28,15 +53,47 @@ fn string_width(font: &PcScreenFont, text: &str) -> u32 { } fn redraw(dt: &mut [u32], font: &PcScreenFont, width: u32, _height: u32) { + const TAG_SPACING: usize = 4; + dt.fill(0); let now = get_real_time().unwrap(); + + let current = CURRENT_WORKSPACE.load(Ordering::Relaxed); + let activity = WORKSPACE_ACTIVITY.load(Ordering::Relaxed); + let max = WORKSPACE_MAX.load(Ordering::Relaxed); + let mut x = 0; + for index in 0..max { + let exists = activity & (1 << index) != 0; + let active = current == index; + if !exists && !active { + continue; + } + let text = format!("{}", index + 1); + let (fg, bg) = if active { + (BLACK, WHITE) + } else { + (WHITE, BLACK) + }; + draw_string(dt, font, 2 + x, 2, &text, width as usize, fg, bg); + x += (font.width() as usize * text.len() + TAG_SPACING) as u32; + } + if let Some(now) = DateTime::from_timestamp(now.seconds() as _, now.subsec_nanos() as _) { let (pm, hour) = now.hour12(); let ampm = if pm { "PM" } else { "AM" }; let text = format!("{}:{:02} {ampm}", hour, now.minute()); let tw = string_width(font, &text); - draw_string(dt, font, width - tw - 4, 2, &text, width as usize); + draw_string( + dt, + font, + width - tw - 4, + 2, + &text, + width as usize, + WHITE, + BLACK, + ); } } @@ -45,6 +102,7 @@ fn run() -> Result { let mut poll = Poll::new()?; let mut timer = TimerFd::new()?; let mut app = Application::new()?; + app.subscribe_to_window_management_events()?; let mut window = Window::new_with_info( &app, CreateWindowInfo { @@ -55,6 +113,18 @@ fn run() -> Result { window.set_on_redraw_requested(move |dt, width, height| { redraw(dt, &font, width, height); }); + app.set_window_management_event_handler(|event| match event { + WindowManagementEvent::WorkspaceChanged { new, .. } => { + CURRENT_WORKSPACE.store(new, Ordering::Relaxed); + true + } + WindowManagementEvent::WorkspaceActivity { mask, max } => { + WORKSPACE_MAX.store(max, Ordering::Relaxed); + WORKSPACE_ACTIVITY.store(mask, Ordering::Relaxed); + true + } + _ => false, + }); app.add_window(window); diff --git a/userspace/colors/src/main.rs b/userspace/colors/src/main.rs index eaa27ac5..2daf45c1 100644 --- a/userspace/colors/src/main.rs +++ b/userspace/colors/src/main.rs @@ -2,7 +2,7 @@ #![feature(map_many_mut, iter_chain)] use std::{ - collections::{BTreeMap, HashMap}, + collections::{BTreeMap, HashMap, HashSet}, marker::PhantomData, os::fd::{AsRawFd, RawFd}, process::{Command, ExitCode}, @@ -11,7 +11,10 @@ use std::{ use cross::mem::SharedMemory; use input::InputState; use libcolors::{ - event::{EventData, KeyEvent, KeyModifiers, KeyboardKeyEvent, WindowEvent, WindowInfo}, + event::{ + EventData, KeyEvent, KeyModifiers, KeyboardKeyEvent, WindowEvent, WindowInfo, + WindowManagementEvent, + }, input::Key, message::{ClientMessage, CreateWindowInfo}, }; @@ -35,6 +38,7 @@ pub struct Server<'a> { input_state: InputState, last_client_id: u32, client_map: HashMap, + wm_clients: HashSet, display: Display, windows: BTreeMap>, @@ -53,6 +57,7 @@ impl<'a> Server<'a> { last_client_id: 0, client_map: HashMap::new(), + wm_clients: HashSet::new(), windows: BTreeMap::new(), display: Display::new(800, 600), @@ -68,10 +73,10 @@ impl<'a> Server<'a> { tx: &mut ServerSender, peer: &PeerAddress, info: CreateWindowInfo, - ) -> Result<(WindowInfo, RawFd), Error> { + ) -> Result<(WindowInfo, RawFd, Option), Error> { let wid = self.last_window_id; self.last_window_id += 1; - let need_focus = self + let (need_focus, workspace) = self .display .create_window(wid, &info.ty) .expect("TODO: handle create window failed"); @@ -112,7 +117,7 @@ impl<'a> Server<'a> { } self.redraw_all(tx); - Ok((info, fd)) + Ok((info, fd, workspace)) } fn focus_window(&mut self, tx: &mut ServerSender, wid: u32) { @@ -206,6 +211,7 @@ impl<'a> Server<'a> { next ); + let old_workspace = self.display.current_workspace; let old_focus_wid = self.display.current_workspace().focused_window(); let old_focus = old_focus_wid.and_then(|wid| { let window = self.windows.get(&wid)?; @@ -239,6 +245,15 @@ impl<'a> Server<'a> { // Send redraw to all frames self.flush_dirty_frames(tx); self.redraw_all(tx); + + self.send_window_management_event( + tx, + WindowManagementEvent::WorkspaceChanged { + old: old_workspace, + new: next, + }, + ); + true } @@ -253,8 +268,26 @@ impl<'a> Server<'a> { self.flush_dirty_frames(tx); self.redraw_all(tx); + self.send_workspace_activity_event(tx); true } + + fn send_window_management_event(&self, tx: &mut ServerSender, event: WindowManagementEvent) { + for client in self.wm_clients.iter() { + tx.send_event(EventData::WindowManagementEvent(event.clone()), client); + } + } + + fn send_workspace_activity_event(&self, tx: &mut ServerSender) { + let mask = self.display.activity_mask(); + self.send_window_management_event( + tx, + WindowManagementEvent::WorkspaceActivity { + mask, + max: Display::::MAX, + }, + ); + } } impl WindowServer for Server<'_> { @@ -283,9 +316,13 @@ impl WindowServer for Server<'_> { self.client_map.insert(id, peer.clone()); tx.send_event(EventData::ServerHello(id), &peer); } + ClientMessage::SubscribeToWindowManagement => { + self.wm_clients.insert(peer); + } ClientMessage::CreateWindow(info) => { log::info!("{:?}: CreateWindow", peer); - let (info, shm_fd) = self.create_window(&mut surface, tx, &peer, info).unwrap(); + let (info, shm_fd, workspace) = + self.create_window(&mut surface, tx, &peer, info).unwrap(); let window_id = info.window_id; tx.send_event_with_file(EventData::NewWindowInfo(info), &shm_fd, &peer); self.flush_dirty_frames(tx); @@ -294,6 +331,14 @@ impl WindowServer for Server<'_> { &peer, ); surface.present(); + self.send_window_management_event( + tx, + WindowManagementEvent::WindowCreated { + workspace, + wid: window_id, + }, + ); + self.send_workspace_activity_event(tx); } ClientMessage::BlitWindow { window_id, @@ -338,7 +383,9 @@ impl WindowServer for Server<'_> { let window = self.windows.remove(&wid); let old_focus = self.display.current_workspace().focused_window(); if window.is_some() { - if self.display.remove_window(wid) { + let (removed, workspace) = self.display.remove_window(wid); + + if removed { surface.fill(self.background); self.flush_dirty_frames(tx); self.redraw_all(tx); @@ -360,6 +407,12 @@ impl WindowServer for Server<'_> { } surface.present(); + + self.send_window_management_event( + tx, + WindowManagementEvent::WindowRemoved { workspace, wid }, + ); + self.send_workspace_activity_event(tx); } } } diff --git a/userspace/colors/src/wm/display.rs b/userspace/colors/src/wm/display.rs index ed601918..b359671b 100644 --- a/userspace/colors/src/wm/display.rs +++ b/userspace/colors/src/wm/display.rs @@ -39,6 +39,17 @@ impl Display { } } + // Returns a mask, where each bit means a corresponding workspace has at least one window + pub fn activity_mask(&self) -> u32 { + let mut mask = 0; + for (i, workspace) in self.workspaces.iter().enumerate() { + if workspace.has_windows() { + mask |= 1 << i; + } + } + mask + } + pub fn resize(&mut self, width: u32, height: u32) { self.width = width; self.height = height; @@ -86,18 +97,18 @@ impl Display { self.current_workspace().window_frame(wid) } - pub fn create_window(&mut self, wid: T, ty: &WindowType) -> Option { + pub fn create_window(&mut self, wid: T, ty: &WindowType) -> Option<(bool, Option)> { match ty { &WindowType::Reservation(height) => { self.add_reservation(wid, height); - Some(false) + Some((false, None)) } WindowType::Default => { if !self.current_workspace_mut().create_window(wid) { return None; } self.update_reservation_layout(); - Some(true) + Some((true, Some(self.current_workspace))) } } } @@ -114,7 +125,7 @@ impl Display { } } - pub fn remove_window(&mut self, wid: T) -> bool { + pub fn remove_window(&mut self, wid: T) -> (bool, Option) { // TODO check if the window is a reservation let mut reservation_removed = false; self.reservations_top.retain(|res| { @@ -128,17 +139,17 @@ impl Display { if reservation_removed { self.update_layout(true); - return true; + return (true, None); } - for workspace in self.workspaces.iter_mut() { + for (i, workspace) in self.workspaces.iter_mut().enumerate() { if workspace.remove_window(wid) { self.update_reservation_layout(); - return true; + return (true, Some(i)); } } - false + (false, None) } pub fn send_focused_window(&mut self, target: usize) -> bool { diff --git a/userspace/colors/src/wm/workspace.rs b/userspace/colors/src/wm/workspace.rs index 15ce3647..c6f4f09a 100644 --- a/userspace/colors/src/wm/workspace.rs +++ b/userspace/colors/src/wm/workspace.rs @@ -97,6 +97,10 @@ impl Workspace { this } + pub fn has_windows(&self) -> bool { + self.all_windows().next().is_some() + } + pub fn all_windows(&self) -> impl Iterator + '_ { self.nodes.iter().filter_map(|(_, node)| { let window = node.as_window()?; diff --git a/userspace/lib/cross/src/net.rs b/userspace/lib/cross/src/net.rs index 84cafcb9..8ee8d775 100644 --- a/userspace/lib/cross/src/net.rs +++ b/userspace/lib/cross/src/net.rs @@ -15,7 +15,7 @@ pub struct LocalPacketSocket(sys::LocalPacketSocketImpl); #[repr(transparent)] pub struct BorrowedLocalAddress<'a>(sys::BorrowedAddressImpl<'a>); -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Hash, PartialEq, Eq)] #[repr(transparent)] pub struct OwnedLocalAddress(sys::OwnedAddressImpl); diff --git a/userspace/lib/cross/src/sys/yggdrasil/socket.rs b/userspace/lib/cross/src/sys/yggdrasil/socket.rs index c5e7a4e6..28be530b 100644 --- a/userspace/lib/cross/src/sys/yggdrasil/socket.rs +++ b/userspace/lib/cross/src/sys/yggdrasil/socket.rs @@ -24,7 +24,7 @@ pub enum BorrowedAddressImpl<'a> { Anonymous(u64), } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum OwnedAddressImpl { Named(PathBuf), Anonymous(u64), diff --git a/userspace/lib/libcolors/src/application/connection.rs b/userspace/lib/libcolors/src/application/connection.rs index aa3c62d2..7904f01c 100644 --- a/userspace/lib/libcolors/src/application/connection.rs +++ b/userspace/lib/libcolors/src/application/connection.rs @@ -75,7 +75,7 @@ impl Connection { Ok(Some(Event { data, file })) } Err(error) if error.is_would_block() => Ok(None), - Err(error) => Err(error.into()) + Err(error) => Err(error.into()), } } diff --git a/userspace/lib/libcolors/src/application/mod.rs b/userspace/lib/libcolors/src/application/mod.rs index a7d98a91..e2d055ee 100644 --- a/userspace/lib/libcolors/src/application/mod.rs +++ b/userspace/lib/libcolors/src/application/mod.rs @@ -11,7 +11,7 @@ use cross::signal::set_sigint_handler; use crate::{ error::Error, - event::{Event, EventData, WindowEvent}, + event::{Event, EventData, WindowEvent, WindowManagementEvent}, message::ClientMessage, }; @@ -20,9 +20,13 @@ use self::{connection::Connection, window::Window}; pub mod connection; pub mod window; +pub trait WindowManagementEventHandler = Fn(WindowManagementEvent) -> bool; + pub struct Application<'a> { pub(crate) connection: Arc>, windows: BTreeMap>, + + window_management_event_handler: Box, } static EXIT_SIGNAL: AtomicBool = AtomicBool::new(false); @@ -41,9 +45,24 @@ impl<'a> Application<'a> { Ok(Self { connection: Arc::new(Mutex::new(connection)), windows: BTreeMap::new(), + + window_management_event_handler: Box::new(|_| false), }) } + pub fn subscribe_to_window_management_events(&mut self) -> Result<(), Error> { + let mut connection = self.connection.lock().unwrap(); + connection.send(&ClientMessage::SubscribeToWindowManagement)?; + Ok(()) + } + + pub fn set_window_management_event_handler( + &mut self, + handler: H, + ) { + self.window_management_event_handler = Box::new(handler); + } + pub fn add_window(&mut self, window: Window<'a>) { assert!(!self.windows.contains_key(&window.id())); self.windows.insert(window.id(), window); @@ -61,12 +80,20 @@ impl<'a> Application<'a> { } pub fn handle_event(&mut self, event: Event) -> Result<(), Error> { - if let EventData::WindowEvent(window_id, ev) = event.data { - if let Some(window) = self.windows.get_mut(&window_id) { - window.handle_event(ev)?; - } else { - log::warn!("Unknown window ID received: {window_id}"); + match event.data { + EventData::WindowManagementEvent(event) => { + if (self.window_management_event_handler)(event) { + self.redraw()?; + } } + EventData::WindowEvent(window_id, ev) => { + if let Some(window) = self.windows.get_mut(&window_id) { + window.handle_event(ev)?; + } else { + log::warn!("Unknown window ID received: {window_id}"); + } + } + _ => (), } Ok(()) diff --git a/userspace/lib/libcolors/src/event.rs b/userspace/lib/libcolors/src/event.rs index 84223f1c..227addc4 100644 --- a/userspace/lib/libcolors/src/event.rs +++ b/userspace/lib/libcolors/src/event.rs @@ -25,7 +25,7 @@ pub struct KeyModifiers { #[derive(Debug, Serialize, Deserialize)] pub struct KeyEvent { pub modifiers: KeyModifiers, - pub key: Key + pub key: Key, } #[derive(Debug, Serialize, Deserialize)] @@ -38,7 +38,7 @@ pub struct KeyInput { #[derive(Debug, Serialize, Deserialize)] pub struct KeyboardKeyEvent { pub key: Key, - pub state: bool + pub state: bool, } #[derive(Debug, Serialize, Deserialize)] @@ -53,6 +53,14 @@ pub enum WindowEvent { CloseRequested, } +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum WindowManagementEvent { + WindowCreated { workspace: Option, wid: u32 }, + WindowRemoved { workspace: Option, wid: u32 }, + WorkspaceChanged { old: usize, new: usize }, + WorkspaceActivity { mask: u32, max: usize }, +} + #[derive(Debug, Serialize, Deserialize)] pub enum EventData { // Server events @@ -62,6 +70,9 @@ pub enum EventData { // Window events WindowEvent(u32, WindowEvent), + // Window management events (only if subscribed) + WindowManagementEvent(WindowManagementEvent), + // Request-responses ServerHello(u32), NewWindowInfo(WindowInfo), @@ -70,7 +81,7 @@ pub enum EventData { #[derive(Debug)] pub struct Event { pub data: EventData, - pub file: Option + pub file: Option, } impl KeyModifiers { diff --git a/userspace/lib/libcolors/src/message.rs b/userspace/lib/libcolors/src/message.rs index b9862257..1eb34d28 100644 --- a/userspace/lib/libcolors/src/message.rs +++ b/userspace/lib/libcolors/src/message.rs @@ -31,6 +31,7 @@ pub enum ClientMessage { h: u32, }, DestroyWindow(u32), + SubscribeToWindowManagement, } impl From<(u32, ServerMessage)> for EventData { diff --git a/userspace/lib/uipc/src/lib.rs b/userspace/lib/uipc/src/lib.rs index 52bca9a0..c1fb4a98 100644 --- a/userspace/lib/uipc/src/lib.rs +++ b/userspace/lib/uipc/src/lib.rs @@ -37,13 +37,13 @@ impl Error { pub fn is_would_block(&self) -> bool { match self { Self::Io(error) if error.kind() == io::ErrorKind::WouldBlock => true, - _ => false + _ => false, } } } #[repr(transparent)] -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct PeerAddress(OwnedLocalAddress); impl Channel {