colors: add window management events
This commit is contained in:
parent
8493573721
commit
91d05d352f
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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()?;
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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),
|
||||
|
@ -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()),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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(())
|
||||
|
@ -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 {
|
||||
|
@ -31,6 +31,7 @@ pub enum ClientMessage {
|
||||
h: u32,
|
||||
},
|
||||
DestroyWindow(u32),
|
||||
SubscribeToWindowManagement,
|
||||
}
|
||||
|
||||
impl From<(u32, ServerMessage)> for EventData {
|
||||
|
@ -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 {
|
||||
|
Loading…
x
Reference in New Issue
Block a user