mirror of
https://github.com/Zedfrigg/ironbar.git
synced 2025-07-01 18:51:04 +02:00
Merge pull request #53 from JakeStanger/feat/hyprland-workspaces
feat(workspaces): hyprland support
This commit is contained in:
commit
1cdfebf8db
9 changed files with 650 additions and 184 deletions
|
@ -1,17 +1,33 @@
|
|||
use crate::clients::sway::{get_client, get_sub_client};
|
||||
use crate::clients::compositor::{Compositor, WorkspaceUpdate};
|
||||
use crate::config::CommonConfig;
|
||||
use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext};
|
||||
use crate::{await_sync, send_async, try_send};
|
||||
use crate::{send_async, try_send};
|
||||
use color_eyre::{Report, Result};
|
||||
use gtk::prelude::*;
|
||||
use gtk::Button;
|
||||
use serde::Deserialize;
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::HashMap;
|
||||
use swayipc_async::{Workspace, WorkspaceChange, WorkspaceEvent};
|
||||
use tokio::spawn;
|
||||
use tokio::sync::mpsc::{Receiver, Sender};
|
||||
use tracing::trace;
|
||||
|
||||
#[derive(Debug, Deserialize, Clone, Copy, Eq, PartialEq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum SortOrder {
|
||||
/// Shows workspaces in the order they're added
|
||||
Added,
|
||||
/// Shows workspaces in numeric order.
|
||||
/// Named workspaces are added to the end in alphabetical order.
|
||||
Alphanumeric,
|
||||
}
|
||||
|
||||
impl Default for SortOrder {
|
||||
fn default() -> Self {
|
||||
Self::Alphanumeric
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub struct WorkspacesModule {
|
||||
/// Map of actual workspace names to custom names.
|
||||
|
@ -21,16 +37,13 @@ pub struct WorkspacesModule {
|
|||
#[serde(default = "crate::config::default_false")]
|
||||
all_monitors: bool,
|
||||
|
||||
#[serde(default)]
|
||||
sort: SortOrder,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub common: Option<CommonConfig>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum WorkspaceUpdate {
|
||||
Init(Vec<Workspace>),
|
||||
Update(Box<WorkspaceEvent>),
|
||||
}
|
||||
|
||||
/// Creates a button from a workspace
|
||||
fn create_button(
|
||||
name: &str,
|
||||
|
@ -40,6 +53,7 @@ fn create_button(
|
|||
) -> Button {
|
||||
let button = Button::builder()
|
||||
.label(name_map.get(name).map_or(name, String::as_str))
|
||||
.name(name)
|
||||
.build();
|
||||
|
||||
let style_context = button.style_context();
|
||||
|
@ -60,6 +74,27 @@ fn create_button(
|
|||
button
|
||||
}
|
||||
|
||||
fn reorder_workspaces(container: >k::Box) {
|
||||
let mut buttons = container
|
||||
.children()
|
||||
.into_iter()
|
||||
.map(|child| (child.widget_name().to_string(), child))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
buttons.sort_by(|(label_a, _), (label_b, _a)| {
|
||||
match (label_a.parse::<i32>(), label_b.parse::<i32>()) {
|
||||
(Ok(a), Ok(b)) => a.cmp(&b),
|
||||
(Ok(_), Err(_)) => Ordering::Less,
|
||||
(Err(_), Ok(_)) => Ordering::Greater,
|
||||
(Err(_), Err(_)) => label_a.cmp(label_b),
|
||||
}
|
||||
});
|
||||
|
||||
for (i, (_, button)) in buttons.into_iter().enumerate() {
|
||||
container.reorder_child(&button, i as i32);
|
||||
}
|
||||
}
|
||||
|
||||
impl Module<gtk::Box> for WorkspacesModule {
|
||||
type SendMessage = WorkspaceUpdate;
|
||||
type ReceiveMessage = String;
|
||||
|
@ -70,58 +105,33 @@ impl Module<gtk::Box> for WorkspacesModule {
|
|||
|
||||
fn spawn_controller(
|
||||
&self,
|
||||
info: &ModuleInfo,
|
||||
_info: &ModuleInfo,
|
||||
tx: Sender<ModuleUpdateEvent<Self::SendMessage>>,
|
||||
mut rx: Receiver<Self::ReceiveMessage>,
|
||||
) -> Result<()> {
|
||||
let workspaces = {
|
||||
trace!("Getting current workspaces");
|
||||
let workspaces = await_sync(async {
|
||||
let sway = get_client().await;
|
||||
let mut sway = sway.lock().await;
|
||||
sway.get_workspaces().await
|
||||
})?;
|
||||
|
||||
if self.all_monitors {
|
||||
workspaces
|
||||
} else {
|
||||
trace!("Filtering workspaces to current monitor only");
|
||||
workspaces
|
||||
.into_iter()
|
||||
.filter(|workspace| workspace.output == info.output_name)
|
||||
.collect()
|
||||
}
|
||||
};
|
||||
|
||||
try_send!(
|
||||
tx,
|
||||
ModuleUpdateEvent::Update(WorkspaceUpdate::Init(workspaces))
|
||||
);
|
||||
|
||||
// Subscribe & send events
|
||||
spawn(async move {
|
||||
let mut srx = {
|
||||
let sway = get_sub_client();
|
||||
sway.subscribe_workspace()
|
||||
let client =
|
||||
Compositor::get_workspace_client().expect("Failed to get workspace client");
|
||||
client.subscribe_workspace_change()
|
||||
};
|
||||
|
||||
trace!("Set up Sway workspace subscription");
|
||||
|
||||
while let Ok(payload) = srx.recv().await {
|
||||
send_async!(
|
||||
tx,
|
||||
ModuleUpdateEvent::Update(WorkspaceUpdate::Update(payload))
|
||||
);
|
||||
send_async!(tx, ModuleUpdateEvent::Update(payload));
|
||||
}
|
||||
});
|
||||
|
||||
// Change workspace focus
|
||||
spawn(async move {
|
||||
trace!("Setting up UI event handler");
|
||||
let sway = get_client().await;
|
||||
|
||||
while let Some(name) = rx.recv().await {
|
||||
let mut sway = sway.lock().await;
|
||||
sway.run_command(format!("workspace {}", name)).await?;
|
||||
let client =
|
||||
Compositor::get_workspace_client().expect("Failed to get workspace client");
|
||||
client.focus(name)?;
|
||||
}
|
||||
|
||||
Ok::<(), Report>(())
|
||||
|
@ -145,45 +155,74 @@ impl Module<gtk::Box> for WorkspacesModule {
|
|||
let container = container.clone();
|
||||
let output_name = info.output_name.to_string();
|
||||
|
||||
// keep track of whether init event has fired previously
|
||||
// since it fires for every workspace subscriber
|
||||
let mut has_initialized = false;
|
||||
|
||||
context.widget_rx.attach(None, move |event| {
|
||||
match event {
|
||||
WorkspaceUpdate::Init(workspaces) => {
|
||||
trace!("Creating workspace buttons");
|
||||
for workspace in workspaces {
|
||||
let item = create_button(
|
||||
&workspace.name,
|
||||
workspace.focused,
|
||||
&name_map,
|
||||
&context.controller_tx,
|
||||
);
|
||||
container.add(&item);
|
||||
button_map.insert(workspace.name, item);
|
||||
if !has_initialized {
|
||||
trace!("Creating workspace buttons");
|
||||
for workspace in workspaces {
|
||||
if self.all_monitors || workspace.monitor == output_name {
|
||||
let item = create_button(
|
||||
&workspace.name,
|
||||
workspace.focused,
|
||||
&name_map,
|
||||
&context.controller_tx,
|
||||
);
|
||||
container.add(&item);
|
||||
|
||||
button_map.insert(workspace.name, item);
|
||||
}
|
||||
}
|
||||
|
||||
if self.sort == SortOrder::Alphanumeric {
|
||||
reorder_workspaces(&container);
|
||||
}
|
||||
|
||||
container.show_all();
|
||||
has_initialized = true;
|
||||
}
|
||||
container.show_all();
|
||||
}
|
||||
WorkspaceUpdate::Update(event) if event.change == WorkspaceChange::Focus => {
|
||||
let old = event
|
||||
.old
|
||||
.and_then(|old| old.name)
|
||||
.and_then(|name| button_map.get(&name));
|
||||
WorkspaceUpdate::Focus { old, new } => {
|
||||
let old = button_map.get(&old.name);
|
||||
if let Some(old) = old {
|
||||
old.style_context().remove_class("focused");
|
||||
}
|
||||
|
||||
let new = event
|
||||
.current
|
||||
.and_then(|old| old.name)
|
||||
.and_then(|new| button_map.get(&new));
|
||||
let new = button_map.get(&new.name);
|
||||
if let Some(new) = new {
|
||||
new.style_context().add_class("focused");
|
||||
}
|
||||
}
|
||||
WorkspaceUpdate::Update(event) if event.change == WorkspaceChange::Init => {
|
||||
if let Some(workspace) = event.current {
|
||||
if self.all_monitors
|
||||
|| workspace.output.unwrap_or_default() == output_name
|
||||
{
|
||||
let name = workspace.name.unwrap_or_default();
|
||||
WorkspaceUpdate::Add(workspace) => {
|
||||
if self.all_monitors || workspace.monitor == output_name {
|
||||
let name = workspace.name;
|
||||
let item = create_button(
|
||||
&name,
|
||||
workspace.focused,
|
||||
&name_map,
|
||||
&context.controller_tx,
|
||||
);
|
||||
|
||||
container.add(&item);
|
||||
if self.sort == SortOrder::Alphanumeric {
|
||||
reorder_workspaces(&container);
|
||||
}
|
||||
|
||||
item.show();
|
||||
|
||||
if !name.is_empty() {
|
||||
button_map.insert(name, item);
|
||||
}
|
||||
}
|
||||
}
|
||||
WorkspaceUpdate::Move(workspace) => {
|
||||
if !self.all_monitors {
|
||||
if workspace.monitor == output_name {
|
||||
let name = workspace.name;
|
||||
let item = create_button(
|
||||
&name,
|
||||
workspace.focused,
|
||||
|
@ -191,49 +230,28 @@ impl Module<gtk::Box> for WorkspacesModule {
|
|||
&context.controller_tx,
|
||||
);
|
||||
|
||||
item.show();
|
||||
container.add(&item);
|
||||
|
||||
if self.sort == SortOrder::Alphanumeric {
|
||||
reorder_workspaces(&container);
|
||||
}
|
||||
|
||||
item.show();
|
||||
|
||||
if !name.is_empty() {
|
||||
button_map.insert(name, item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
WorkspaceUpdate::Update(event) if event.change == WorkspaceChange::Move => {
|
||||
if let Some(workspace) = event.current {
|
||||
if !self.all_monitors {
|
||||
if workspace.output.unwrap_or_default() == output_name {
|
||||
let name = workspace.name.unwrap_or_default();
|
||||
let item = create_button(
|
||||
&name,
|
||||
workspace.focused,
|
||||
&name_map,
|
||||
&context.controller_tx,
|
||||
);
|
||||
|
||||
item.show();
|
||||
container.add(&item);
|
||||
|
||||
if !name.is_empty() {
|
||||
button_map.insert(name, item);
|
||||
}
|
||||
} else if let Some(item) =
|
||||
button_map.get(&workspace.name.unwrap_or_default())
|
||||
{
|
||||
container.remove(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
WorkspaceUpdate::Update(event) if event.change == WorkspaceChange::Empty => {
|
||||
if let Some(workspace) = event.current {
|
||||
if let Some(item) = button_map.get(&workspace.name.unwrap_or_default())
|
||||
{
|
||||
} else if let Some(item) = button_map.get(&workspace.name) {
|
||||
container.remove(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
WorkspaceUpdate::Remove(workspace) => {
|
||||
let button = button_map.get(&workspace.name);
|
||||
if let Some(item) = button {
|
||||
container.remove(item);
|
||||
}
|
||||
}
|
||||
WorkspaceUpdate::Update(_) => {}
|
||||
};
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue