step 5: live Hyprland workspace buttons via EventStream
This commit is contained in:
parent
542cbae939
commit
241e3bc2cb
4 changed files with 109 additions and 10 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -12,6 +12,7 @@ checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
||||||
name = "aster"
|
name = "aster"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"futures-lite",
|
||||||
"gtk4",
|
"gtk4",
|
||||||
"gtk4-layer-shell",
|
"gtk4-layer-shell",
|
||||||
"hyprland",
|
"hyprland",
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,8 @@ edition = "2021"
|
||||||
gtk4 = { version = "0.11", features = ["v4_12"] }
|
gtk4 = { version = "0.11", features = ["v4_12"] }
|
||||||
gtk4-layer-shell = "0.8"
|
gtk4-layer-shell = "0.8"
|
||||||
relm4 = { version = "0.11", features = ["macros"] }
|
relm4 = { version = "0.11", features = ["macros"] }
|
||||||
hyprland = "0.4.0-beta.3"
|
hyprland = { version = "0.4.0-beta.3", features = ["tokio"] }
|
||||||
|
futures-lite = "2"
|
||||||
zbus = { version = "5", default-features = false, features = ["tokio"] }
|
zbus = { version = "5", default-features = false, features = ["tokio"] }
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
|
|
||||||
|
|
@ -1 +1,50 @@
|
||||||
|
use futures_lite::StreamExt;
|
||||||
|
use gtk4::prelude::*;
|
||||||
|
use hyprland::{
|
||||||
|
data::{Workspace, Workspaces},
|
||||||
|
event_listener::{Event, EventStream},
|
||||||
|
prelude::*,
|
||||||
|
shared::WorkspaceId,
|
||||||
|
};
|
||||||
|
use relm4::ComponentSender;
|
||||||
|
|
||||||
|
use crate::AppInput;
|
||||||
|
|
||||||
|
pub fn spawn_watcher(sender: ComponentSender<crate::App>) {
|
||||||
|
relm4::spawn(async move {
|
||||||
|
if let Ok(ws) = Workspaces::get_async().await {
|
||||||
|
sender.input(AppInput::WorkspaceList(ws.to_vec()));
|
||||||
|
}
|
||||||
|
if let Ok(active) = Workspace::get_active_async().await {
|
||||||
|
sender.input(AppInput::ActiveWorkspace(active.id));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut stream = EventStream::new();
|
||||||
|
while let Some(Ok(event)) = stream.next().await {
|
||||||
|
match event {
|
||||||
|
Event::WorkspaceChanged(data) => {
|
||||||
|
sender.input(AppInput::ActiveWorkspace(data.id));
|
||||||
|
}
|
||||||
|
Event::WorkspaceAdded(_) | Event::WorkspaceDeleted(_) => {
|
||||||
|
if let Ok(ws) = Workspaces::get_async().await {
|
||||||
|
sender.input(AppInput::WorkspaceList(ws.to_vec()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn make_button(id: WorkspaceId, name: &str, active: WorkspaceId) -> gtk4::Button {
|
||||||
|
let btn = gtk4::Button::with_label(name);
|
||||||
|
btn.add_css_class("workspace-btn");
|
||||||
|
if id == active {
|
||||||
|
btn.add_css_class("active");
|
||||||
|
}
|
||||||
|
btn.connect_clicked(move |_| {
|
||||||
|
use hyprland::dispatch::{Dispatch, DispatchType, WorkspaceIdentifierWithSpecial};
|
||||||
|
let _ = Dispatch::call(DispatchType::Workspace(WorkspaceIdentifierWithSpecial::Id(id)));
|
||||||
|
});
|
||||||
|
btn
|
||||||
|
}
|
||||||
|
|
|
||||||
66
src/main.rs
66
src/main.rs
|
|
@ -4,14 +4,27 @@ mod theme;
|
||||||
|
|
||||||
use gtk4::prelude::*;
|
use gtk4::prelude::*;
|
||||||
use gtk4_layer_shell::{Edge, Layer, LayerShell};
|
use gtk4_layer_shell::{Edge, Layer, LayerShell};
|
||||||
|
use hyprland::data::Workspace;
|
||||||
|
use hyprland::shared::WorkspaceId;
|
||||||
use relm4::prelude::*;
|
use relm4::prelude::*;
|
||||||
|
|
||||||
struct App;
|
pub struct App {
|
||||||
|
workspaces: Vec<Workspace>,
|
||||||
|
active_ws: WorkspaceId,
|
||||||
|
// Stored handle so update() can manipulate the live widget directly.
|
||||||
|
workspace_box: gtk4::Box,
|
||||||
|
}
|
||||||
|
|
||||||
#[relm4::component]
|
#[derive(Debug)]
|
||||||
|
pub enum AppInput {
|
||||||
|
WorkspaceList(Vec<Workspace>),
|
||||||
|
ActiveWorkspace(WorkspaceId),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[relm4::component(pub)]
|
||||||
impl SimpleComponent for App {
|
impl SimpleComponent for App {
|
||||||
type Init = ();
|
type Init = ();
|
||||||
type Input = ();
|
type Input = AppInput;
|
||||||
type Output = ();
|
type Output = ();
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
|
|
@ -24,11 +37,13 @@ impl SimpleComponent for App {
|
||||||
#[wrap(Some)]
|
#[wrap(Some)]
|
||||||
set_start_widget = >k::Box {
|
set_start_widget = >k::Box {
|
||||||
set_orientation: gtk::Orientation::Horizontal,
|
set_orientation: gtk::Orientation::Horizontal,
|
||||||
set_spacing: 4,
|
set_spacing: 0,
|
||||||
set_margin_start: 8,
|
set_margin_start: 8,
|
||||||
|
|
||||||
gtk::Label {
|
#[name = "workspace_box"]
|
||||||
set_label: "1 2 3",
|
gtk::Box {
|
||||||
|
set_orientation: gtk::Orientation::Horizontal,
|
||||||
|
set_spacing: 4,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -54,7 +69,7 @@ impl SimpleComponent for App {
|
||||||
fn init(
|
fn init(
|
||||||
_: Self::Init,
|
_: Self::Init,
|
||||||
root: Self::Root,
|
root: Self::Root,
|
||||||
_sender: ComponentSender<Self>,
|
sender: ComponentSender<Self>,
|
||||||
) -> ComponentParts<Self> {
|
) -> ComponentParts<Self> {
|
||||||
root.init_layer_shell();
|
root.init_layer_shell();
|
||||||
root.set_layer(Layer::Top);
|
root.set_layer(Layer::Top);
|
||||||
|
|
@ -63,15 +78,48 @@ impl SimpleComponent for App {
|
||||||
root.set_anchor(Edge::Right, true);
|
root.set_anchor(Edge::Right, true);
|
||||||
root.set_exclusive_zone(32);
|
root.set_exclusive_zone(32);
|
||||||
|
|
||||||
let model = App;
|
// Placeholder until view_output! gives us the real handle.
|
||||||
|
let mut model = App {
|
||||||
|
workspaces: vec![],
|
||||||
|
active_ws: 1,
|
||||||
|
workspace_box: gtk4::Box::new(gtk4::Orientation::Horizontal, 4),
|
||||||
|
};
|
||||||
let widgets = view_output!();
|
let widgets = view_output!();
|
||||||
|
|
||||||
|
// Swap in the actual widget so update() can reach it.
|
||||||
|
model.workspace_box = widgets.workspace_box.clone();
|
||||||
|
|
||||||
theme::apply();
|
theme::apply();
|
||||||
|
bar::workspaces::spawn_watcher(sender);
|
||||||
|
|
||||||
ComponentParts { model, widgets }
|
ComponentParts { model, widgets }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, _: Self::Input, _: ComponentSender<Self>) {}
|
fn update(&mut self, msg: Self::Input, _: ComponentSender<Self>) {
|
||||||
|
match msg {
|
||||||
|
AppInput::WorkspaceList(list) => {
|
||||||
|
let mut sorted = list;
|
||||||
|
sorted.sort_by_key(|w| w.id);
|
||||||
|
self.workspaces = sorted;
|
||||||
|
}
|
||||||
|
AppInput::ActiveWorkspace(id) => {
|
||||||
|
self.active_ws = id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.rebuild_buttons();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl App {
|
||||||
|
fn rebuild_buttons(&self) {
|
||||||
|
while let Some(child) = self.workspace_box.first_child() {
|
||||||
|
self.workspace_box.remove(&child);
|
||||||
|
}
|
||||||
|
for ws in &self.workspaces {
|
||||||
|
self.workspace_box
|
||||||
|
.append(&bar::workspaces::make_button(ws.id, &ws.name, self.active_ws));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue