colors: add window management events

This commit is contained in:
Mark Poliakov 2025-03-03 13:47:06 +02:00
parent 8493573721
commit 91d05d352f
11 changed files with 210 additions and 33 deletions

View File

@ -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<ExitCode, Error> {
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<ExitCode, Error> {
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);

View File

@ -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<u32, PeerAddress>,
wm_clients: HashSet<PeerAddress>,
display: Display<u32>,
windows: BTreeMap<u32, Window<'a>>,
@ -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<usize>), 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::<u32>::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);
}
}
}

View File

@ -39,6 +39,17 @@ impl<T: Eq + Hash + Copy> Display<T> {
}
}
// 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<T: Eq + Hash + Copy> Display<T> {
self.current_workspace().window_frame(wid)
}
pub fn create_window(&mut self, wid: T, ty: &WindowType) -> Option<bool> {
pub fn create_window(&mut self, wid: T, ty: &WindowType) -> Option<(bool, Option<usize>)> {
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<T: Eq + Hash + Copy> Display<T> {
}
}
pub fn remove_window(&mut self, wid: T) -> bool {
pub fn remove_window(&mut self, wid: T) -> (bool, Option<usize>) {
// TODO check if the window is a reservation
let mut reservation_removed = false;
self.reservations_top.retain(|res| {
@ -128,17 +139,17 @@ impl<T: Eq + Hash + Copy> Display<T> {
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 {

View File

@ -97,6 +97,10 @@ impl<T: Eq + Hash + Copy> Workspace<T> {
this
}
pub fn has_windows(&self) -> bool {
self.all_windows().next().is_some()
}
pub fn all_windows(&self) -> impl Iterator<Item = (T, &NodeLayout)> + '_ {
self.nodes.iter().filter_map(|(_, node)| {
let window = node.as_window()?;

View File

@ -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);

View File

@ -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),

View File

@ -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()),
}
}

View File

@ -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<Mutex<Connection>>,
windows: BTreeMap<u32, Window<'a>>,
window_management_event_handler: Box<dyn WindowManagementEventHandler>,
}
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<H: WindowManagementEventHandler + 'static>(
&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(())

View File

@ -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<usize>, wid: u32 },
WindowRemoved { workspace: Option<usize>, 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<OwnedFd>
pub file: Option<OwnedFd>,
}
impl KeyModifiers {

View File

@ -31,6 +31,7 @@ pub enum ClientMessage {
h: u32,
},
DestroyWindow(u32),
SubscribeToWindowManagement,
}
impl From<(u32, ServerMessage)> for EventData {

View File

@ -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 {