From 1ba6c8a0263f26ebeeec6e098ca125e2488478d3 Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Tue, 21 Apr 2026 22:34:41 -0400 Subject: [PATCH] Order displays based on logical position --- lib/widgets/bar.dart | 178 ++++++++++++++--------------- rust/Cargo.toml | 2 +- rust/src/api/workspace_api.rs | 16 ++- rust/src/internal/wayland.rs | 208 +++++++++++++++++++++------------- 4 files changed, 226 insertions(+), 178 deletions(-) diff --git a/lib/widgets/bar.dart b/lib/widgets/bar.dart index 66eeb10..04044c8 100644 --- a/lib/widgets/bar.dart +++ b/lib/widgets/bar.dart @@ -9,104 +9,98 @@ class Bar extends ConsumerWidget { const Bar({super.key}); @override - Widget build(BuildContext context, WidgetRef ref) => Center( - child: Padding( - padding: EdgeInsets.symmetric(horizontal: 8), - child: Stack( - children: - [ - ref - .watch(WorkspacesController.provider) - .when( - error: (error, stackTrace) => [Text(error.toString())], - loading: () => [SizedBox.shrink()], - data: (value) => value - .map( - (group) => Row( - children: group - .map( - (workspace) => IconButton( - onPressed: () => workspace.activate(), - icon: Icon( - workspace.activated - ? Icons.circle - : Icons.circle_outlined, - ), - ), - ) - .toList(), - ), - ) - .toList(), - ), - [ - TextButton( - onPressed: () {}, - child: Text( - DateFormat.Hm().format( - ref - .watch(TimeController.provider) - .when( - data: (time) => time, - loading: DateTime.now, - error: (_, _) => DateTime.now(), - ), - ), - ), - ), - ], - - [ - Row( - children: [ - IconButton(onPressed: () {}, icon: Icon(Icons.wifi)), - IconButton( - onPressed: () {}, - icon: Icon(Icons.bluetooth), - ), - IconButton( - onPressed: () {}, - icon: Icon(Icons.volume_off), - ), - ], - ), - ], - ] - .mapIndexed( - (index, children) => Align( - alignment: [ - Alignment.centerLeft, - Alignment.center, - Alignment.centerRight, - ][index], - child: Row( - spacing: 8, - mainAxisSize: MainAxisSize.min, - children: children + Widget build(BuildContext context, WidgetRef ref) => Padding( + padding: EdgeInsets.symmetric(horizontal: 8), + child: Stack( + children: + [ + ref + .watch(WorkspacesController.provider) + .when( + error: (error, stackTrace) => [Text(error.toString())], + loading: () => [SizedBox.shrink()], + data: (value) => value .map( - (child) => Padding( - padding: EdgeInsetsGeometry.directional( - bottom: 6, - ), - child: Container( - height: 42, - padding: EdgeInsets.symmetric(horizontal: 12), - decoration: BoxDecoration( - color: Theme.of( - context, - ).colorScheme.surfaceContainerLow, - borderRadius: BorderRadius.circular(999), - ), - child: child, - ), + (group) => Row( + children: group + .map( + (workspace) => IconButton( + onPressed: () => workspace.activate(), + icon: Icon( + workspace.activated + ? Icons.circle + : Icons.circle_outlined, + ), + ), + ) + .toList(), ), ) .toList(), ), + [ + TextButton( + onPressed: () {}, + child: Text( + DateFormat.Hm().format( + ref + .watch(TimeController.provider) + .when( + data: (time) => time, + loading: DateTime.now, + error: (_, _) => DateTime.now(), + ), + ), + ), ), - ) - .toList(), - ), + ], + + [ + Row( + children: [ + IconButton(onPressed: () {}, icon: Icon(Icons.wifi)), + IconButton(onPressed: () {}, icon: Icon(Icons.bluetooth)), + + IconButton( + onPressed: () {}, + icon: Icon(Icons.volume_off), + ), + ], + ), + ], + ] + .mapIndexed( + (index, children) => Align( + alignment: [ + Alignment.centerLeft, + Alignment.center, + Alignment.centerRight, + ][index], + child: Row( + spacing: 8, + mainAxisSize: MainAxisSize.min, + children: children + .map( + (child) => Padding( + padding: EdgeInsetsGeometry.directional(bottom: 6), + child: Container( + height: 42, + padding: EdgeInsets.symmetric(horizontal: 12), + decoration: BoxDecoration( + color: Theme.of( + context, + ).colorScheme.surfaceContainerLow, + borderRadius: BorderRadius.circular(999), + ), + child: child, + ), + ), + ) + .toList(), + ), + ), + ) + .toList(), ), ); } diff --git a/rust/Cargo.toml b/rust/Cargo.toml index f0d324e..20899ef 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -13,7 +13,7 @@ serde = { version = "1", features = ["derive"] } serde_json = "1" wayland-client = "0.31" -wayland-protocols = { version = "0.32.12", features = ["client", "staging"] } +wayland-protocols = { version = "0.32.12", features = ["client", "staging", "unstable"] } calloop = "0.14.4" calloop-wayland-source = "0.4.1" diff --git a/rust/src/api/workspace_api.rs b/rust/src/api/workspace_api.rs index fadd9da..6941e94 100644 --- a/rust/src/api/workspace_api.rs +++ b/rust/src/api/workspace_api.rs @@ -1,10 +1,13 @@ use crate::{frb_generated::StreamSink, internal::wayland::AppState}; -use std::{collections::HashMap, sync::{Mutex, OnceLock}}; use serde_json::Result; +use std::{ + collections::HashMap, + sync::{Mutex, OnceLock}, +}; use calloop::{ + channel::{channel, Channel, Sender}, EventLoop, - channel::{Channel, Sender, channel}, }; use calloop_wayland_source::WaylandSource; @@ -25,8 +28,8 @@ pub struct Workspace { impl Workspace { pub fn activate(&self) { if let Some(tx) = controller().lock().unwrap().as_ref() { - let _ = tx.send(self.id); - } + let _ = tx.send(self.id); + } } } @@ -39,10 +42,11 @@ pub fn listen_workspaces(sink: StreamSink>>) -> Result<()> { let qh = event_queue.handle(); let mut state = AppState { - outputs: HashMap::new(), + outputs: HashMap::new(), workspaces: HashMap::new(), workspace_groups: HashMap::new(), workspace_handles: HashMap::new(), + xdg_output_manager: None, sink, }; @@ -69,4 +73,4 @@ pub fn listen_workspaces(sink: StreamSink>>) -> Result<()> { while let Ok(_) = event_loop.dispatch(None, &mut state) {} Ok(()) -} \ No newline at end of file +} diff --git a/rust/src/internal/wayland.rs b/rust/src/internal/wayland.rs index 87e3461..75920b6 100644 --- a/rust/src/internal/wayland.rs +++ b/rust/src/internal/wayland.rs @@ -2,11 +2,16 @@ use crate::{frb_generated::StreamSink, workspace_api::Workspace}; use std::collections::HashMap; use wayland_client::{ - self, event_created_child, protocol::{wl_registry, wl_output}, Connection, Dispatch, Proxy, QueueHandle, + self, event_created_child, + protocol::{wl_output, wl_registry}, + Connection, Dispatch, Proxy, QueueHandle, }; -use wayland_protocols::ext::workspace::v1::client::{ - ext_workspace_group_handle_v1, ext_workspace_handle_v1, ext_workspace_manager_v1, +use wayland_protocols::{ + ext::workspace::v1::client::{ + ext_workspace_group_handle_v1, ext_workspace_handle_v1, ext_workspace_manager_v1, + }, + xdg::xdg_output::zv1::client::{zxdg_output_manager_v1, zxdg_output_v1}, }; pub struct WorkspaceHandles { @@ -29,34 +34,34 @@ pub struct AppState { pub workspace_groups: HashMap, pub workspace_handles: HashMap, pub sink: StreamSink>>, + pub xdg_output_manager: Option, } impl AppState { fn emit(&self) { - let mut groups: Vec<_> = self.workspace_groups.values().collect(); + let mut groups: Vec<_> = self.workspace_groups.values().collect(); - groups.sort_by_key(|group| { - group.output_id.as_ref().and_then(|o| { - self.outputs.get(o).map(|info| info.coords.0) - }) - }); + groups.sort_by_key(|group| { + group + .output_id + .as_ref() + .and_then(|o| self.outputs.get(o).map(|info| info.coords.0)) + }); let result: Vec> = groups - .into_iter() - .map(|group| { - let mut workspaces: Vec = group - .children - .iter() - .filter_map(|id| self.workspaces.get(id)) - .cloned() - .collect(); + .into_iter() + .map(|group| { + let mut workspaces: Vec = group + .children + .iter() + .filter_map(|id| self.workspaces.get(id)) + .cloned() + .collect(); - workspaces.sort_by_key(|ws| - ws.coords.as_ref().map(|coords| coords.0) - ); - workspaces - }) - .collect(); + workspaces.sort_by_key(|ws| ws.coords.as_ref().map(|coords| coords.0)); + workspaces + }) + .collect(); self.sink.add(result).expect("Updating stream failed"); } @@ -64,7 +69,7 @@ impl AppState { impl Dispatch for AppState { fn event( - _state: &mut Self, + state: &mut Self, registry: &wl_registry::WlRegistry, event: wl_registry::Event, _data: &(), @@ -77,27 +82,34 @@ impl Dispatch for AppState { version, } = event { - match interface.as_str() { - "ext_workspace_manager_v1" => { - registry.bind::( - name, - version, - qh, - (), - ); - } + match interface.as_str() { + "ext_workspace_manager_v1" => { + registry.bind::( + name, + version, + qh, + (), + ); + } - "wl_output" => { - registry.bind::( - name, - version, - qh, - (), - ); - } + "zxdg_output_manager_v1" => { + let manager: zxdg_output_manager_v1::ZxdgOutputManagerV1 = + registry.bind::( + name, + version, + qh, + (), + ); - _ => {} - } + state.xdg_output_manager = Some(manager); + } + + "wl_output" => { + registry.bind::(name, version, qh, ()); + } + + _ => {} + } } } } @@ -117,18 +129,18 @@ impl Dispatch for AppState workspace.id().protocol_id(), Workspace { activated: false, - id: workspace.id().protocol_id(), + id: workspace.id().protocol_id(), coords: None, }, ); - state.workspace_handles.insert( - workspace.id().protocol_id(), - WorkspaceHandles { - handle: workspace, - manager_handle: proxy.clone(), - } - ); + state.workspace_handles.insert( + workspace.id().protocol_id(), + WorkspaceHandles { + handle: workspace, + manager_handle: proxy.clone(), + }, + ); } _ => {} } @@ -207,7 +219,7 @@ impl Dispatch for event: ext_workspace_group_handle_v1::Event, _data: &(), _conn: &Connection, - _qh: &QueueHandle, + qh: &QueueHandle, ) { let group_id = proxy.id(); @@ -215,26 +227,30 @@ impl Dispatch for ext_workspace_group_handle_v1::Event::WorkspaceEnter { workspace } => { state .workspace_groups - .entry(group_id.protocol_id()) - .or_insert_with(|| WorkspaceGroup { - output_id: None, - children: Vec::new(), - }) - .children - .push(workspace.id().protocol_id()); + .entry(group_id.protocol_id()) + .or_insert_with(|| WorkspaceGroup { + output_id: None, + children: Vec::new(), + }) + .children + .push(workspace.id().protocol_id()); state.emit(); } - ext_workspace_group_handle_v1::Event::OutputEnter { output } => { - state - .workspace_groups - .entry(group_id.protocol_id()) - .or_insert_with(|| WorkspaceGroup { - output_id: None, - children: Vec::new(), - }) - .output_id = Some(output.id().protocol_id()); - state.emit(); + ext_workspace_group_handle_v1::Event::OutputEnter { output } => { + if let Some(manager) = &state.xdg_output_manager { + let xdg_output = manager.get_xdg_output(&output, qh, ()); + state + .workspace_groups + .entry(group_id.protocol_id()) + .or_insert_with(|| WorkspaceGroup { + output_id: None, + children: Vec::new(), + }) + .output_id = Some(xdg_output.id().protocol_id()); + + state.emit(); + } } ext_workspace_group_handle_v1::Event::WorkspaceLeave { workspace } => { @@ -247,25 +263,59 @@ impl Dispatch for } } -impl Dispatch for AppState { +impl Dispatch for AppState { + fn event( + _state: &mut Self, + _proxy: &zxdg_output_manager_v1::ZxdgOutputManagerV1, + _event: ::Event, + _data: &(), + _conn: &Connection, + _qhandle: &QueueHandle, + ) { + // Do nothing, for now + } + + event_created_child!( + AppState, + zxdg_output_manager_v1::ZxdgOutputManagerV1, + [ + zxdg_output_manager_v1::REQ_GET_XDG_OUTPUT_OPCODE => ( + zxdg_output_v1::ZxdgOutputV1, + () + ) + ] + ); +} + +impl Dispatch for AppState { fn event( state: &mut Self, - proxy: &wl_output::WlOutput, - event: wl_output::Event, + proxy: &zxdg_output_v1::ZxdgOutputV1, + event: zxdg_output_v1::Event, _data: &(), _conn: &Connection, _qh: &QueueHandle, ) { match event { - wl_output::Event::Geometry { - x, - y, - .. - } => { - state.outputs.insert(proxy.id().protocol_id(), Output { coords: ( x, y ) }); + zxdg_output_v1::Event::LogicalPosition { x, y } => { + state + .outputs + .insert(proxy.id().protocol_id(), Output { coords: (x, y) }); state.emit(); } _ => {} } } -} \ No newline at end of file +} +impl Dispatch for AppState { + fn event( + _state: &mut Self, + _proxy: &wl_output::WlOutput, + _event: wl_output::Event, + _data: &(), + _conn: &Connection, + _qh: &QueueHandle, + ) { + // Do nothing, just bind + } +}