1
0
Fork 0
mirror of https://github.com/Zedfrigg/ironbar.git synced 2025-07-01 10:41:03 +02:00

feat: new focused window module

This commit is contained in:
Jake Stanger 2022-08-14 20:40:11 +01:00
parent e416e03b0a
commit dc14cb003f
No known key found for this signature in database
GPG key ID: C51FC8F9CB0BEA61
13 changed files with 222 additions and 88 deletions

View file

@ -48,14 +48,14 @@ fn load_modules(
right: &gtk::Box, right: &gtk::Box,
app: &Application, app: &Application,
config: Config, config: Config,
output_name: &str output_name: &str,
) { ) {
if let Some(modules) = config.left { if let Some(modules) = config.left {
let info = ModuleInfo { let info = ModuleInfo {
app, app,
location: ModuleLocation::Left, location: ModuleLocation::Left,
bar_position: &config.position, bar_position: &config.position,
output_name output_name,
}; };
add_modules(left, modules, info); add_modules(left, modules, info);
@ -66,7 +66,7 @@ fn load_modules(
app, app,
location: ModuleLocation::Center, location: ModuleLocation::Center,
bar_position: &config.position, bar_position: &config.position,
output_name output_name,
}; };
add_modules(center, modules, info); add_modules(center, modules, info);
@ -77,7 +77,7 @@ fn load_modules(
app, app,
location: ModuleLocation::Right, location: ModuleLocation::Right,
bar_position: &config.position, bar_position: &config.position,
output_name output_name,
}; };
add_modules(right, modules, info); add_modules(right, modules, info);
@ -122,6 +122,11 @@ fn add_modules(content: &gtk::Box, modules: Vec<ModuleConfig>, info: ModuleInfo)
widget.set_widget_name("script"); widget.set_widget_name("script");
content.add(&widget); content.add(&widget);
} }
ModuleConfig::Focused(module) => {
let widget = module.into_widget(&info);
widget.set_widget_name("focused");
content.add(&widget);
}
} }
} }
} }
@ -137,8 +142,16 @@ fn setup_layer_shell(win: &ApplicationWindow, monitor: &Monitor, position: &BarP
gtk_layer_shell::set_margin(win, gtk_layer_shell::Edge::Left, 0); gtk_layer_shell::set_margin(win, gtk_layer_shell::Edge::Left, 0);
gtk_layer_shell::set_margin(win, gtk_layer_shell::Edge::Right, 0); gtk_layer_shell::set_margin(win, gtk_layer_shell::Edge::Right, 0);
gtk_layer_shell::set_anchor(win, gtk_layer_shell::Edge::Top, position == &BarPosition::Top); gtk_layer_shell::set_anchor(
gtk_layer_shell::set_anchor(win, gtk_layer_shell::Edge::Bottom, position == &BarPosition::Bottom); win,
gtk_layer_shell::Edge::Top,
position == &BarPosition::Top,
);
gtk_layer_shell::set_anchor(
win,
gtk_layer_shell::Edge::Bottom,
position == &BarPosition::Bottom,
);
gtk_layer_shell::set_anchor(win, gtk_layer_shell::Edge::Left, true); gtk_layer_shell::set_anchor(win, gtk_layer_shell::Edge::Left, true);
gtk_layer_shell::set_anchor(win, gtk_layer_shell::Edge::Right, true); gtk_layer_shell::set_anchor(win, gtk_layer_shell::Edge::Right, true);
} }

View file

@ -1,4 +1,5 @@
use crate::modules::clock::ClockModule; use crate::modules::clock::ClockModule;
use crate::modules::focused::FocusedModule;
use crate::modules::launcher::LauncherModule; use crate::modules::launcher::LauncherModule;
use crate::modules::mpd::MpdModule; use crate::modules::mpd::MpdModule;
use crate::modules::script::ScriptModule; use crate::modules::script::ScriptModule;
@ -19,13 +20,14 @@ pub enum ModuleConfig {
SysInfo(SysInfoModule), SysInfo(SysInfoModule),
Launcher(LauncherModule), Launcher(LauncherModule),
Script(ScriptModule), Script(ScriptModule),
Focused(FocusedModule),
} }
#[derive(Debug, Deserialize, Clone, PartialEq)] #[derive(Debug, Deserialize, Clone, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")] #[serde(rename_all = "kebab-case")]
pub enum BarPosition { pub enum BarPosition {
Top, Top,
Bottom Bottom,
} }
impl Default for BarPosition { impl Default for BarPosition {
@ -83,3 +85,10 @@ impl Config {
}) })
} }
} }
pub const fn default_false() -> bool {
false
}
pub const fn default_true() -> bool {
true
}

View file

@ -1,24 +1,21 @@
mod bar; mod bar;
mod collection; mod collection;
mod config; mod config;
mod icon;
mod modules; mod modules;
mod popup; mod popup;
mod style; mod style;
mod sway;
use crate::bar::create_bar; use crate::bar::create_bar;
use crate::config::Config; use crate::config::Config;
use crate::style::load_css; use crate::style::load_css;
use crate::sway::SwayOutput;
use dirs::config_dir; use dirs::config_dir;
use gtk::prelude::*; use gtk::prelude::*;
use gtk::{gdk, Application}; use gtk::{gdk, Application};
use ksway::client::Client; use ksway::client::Client;
use ksway::IpcCommand; use ksway::IpcCommand;
use serde::Deserialize;
#[derive(Deserialize)]
struct SwayOutput {
name: String,
}
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
@ -27,8 +24,11 @@ async fn main() {
.build(); .build();
let mut sway_client = Client::connect().expect("Failed to connect to Sway IPC"); let mut sway_client = Client::connect().expect("Failed to connect to Sway IPC");
let outputs = sway_client.ipc(IpcCommand::GetOutputs).expect("Failed to get Sway outputs"); let outputs = sway_client
let outputs = serde_json::from_slice::<Vec<SwayOutput>>(&outputs).expect("Failed to deserialize outputs message from Sway IPC"); .ipc(IpcCommand::GetOutputs)
.expect("Failed to get Sway outputs");
let outputs = serde_json::from_slice::<Vec<SwayOutput>>(&outputs)
.expect("Failed to deserialize outputs message from Sway IPC");
app.connect_activate(move |app| { app.connect_activate(move |app| {
let config = Config::load().unwrap_or_default(); let config = Config::load().unwrap_or_default();
@ -42,7 +42,10 @@ async fn main() {
let num_monitors = display.n_monitors(); let num_monitors = display.n_monitors();
for i in 0..num_monitors { for i in 0..num_monitors {
let monitor = display.monitor(i).unwrap(); let monitor = display.monitor(i).unwrap();
let monitor_name = &outputs.get(i as usize).expect("GTK monitor output differs from Sway's").name; let monitor_name = &outputs
.get(i as usize)
.expect("GTK monitor output differs from Sway's")
.name;
let config = config.monitors.as_ref().map_or(&config, |monitor_config| { let config = config.monitors.as_ref().map_or(&config, |monitor_config| {
monitor_config.get(i as usize).unwrap_or(&config) monitor_config.get(i as usize).unwrap_or(&config)

View file

@ -30,7 +30,12 @@ impl Module<Button> for ClockModule {
fn into_widget(self, info: &ModuleInfo) -> Button { fn into_widget(self, info: &ModuleInfo) -> Button {
let button = Button::new(); let button = Button::new();
let popup = Popup::new("popup-clock", info.app, Orientation::Vertical, info.bar_position); let popup = Popup::new(
"popup-clock",
info.app,
Orientation::Vertical,
info.bar_position,
);
popup.add_clock_widgets(); popup.add_clock_widgets();
button.show_all(); button.show_all();

97
src/modules/focused.rs Normal file
View file

@ -0,0 +1,97 @@
use crate::icon;
use crate::modules::{Module, ModuleInfo};
use crate::sway::node::get_open_windows;
use crate::sway::WindowEvent;
use glib::Continue;
use gtk::prelude::*;
use gtk::{IconTheme, Image, Label, Orientation};
use ksway::{Client, IpcEvent};
use serde::Deserialize;
use tokio::task::spawn_blocking;
#[derive(Debug, Deserialize, Clone)]
pub struct FocusedModule {
#[serde(default = "crate::config::default_true")]
show_icon: bool,
#[serde(default = "crate::config::default_true")]
show_title: bool,
#[serde(default = "default_icon_size")]
icon_size: i32,
icon_theme: Option<String>,
}
const fn default_icon_size() -> i32 {
32
}
impl Module<gtk::Box> for FocusedModule {
fn into_widget(self, _info: &ModuleInfo) -> gtk::Box {
let icon_theme = IconTheme::new();
if let Some(theme) = self.icon_theme {
icon_theme.set_custom_theme(Some(&theme));
}
let container = gtk::Box::new(Orientation::Horizontal, 5);
let icon = Image::builder().name("icon").build();
let label = Label::builder().name("label").build();
container.add(&icon);
container.add(&label);
let mut sway = Client::connect().unwrap();
let srx = sway.subscribe(vec![IpcEvent::Window]).unwrap();
let (tx, rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
let focused = get_open_windows(&mut sway)
.into_iter()
.find(|node| node.focused);
if let Some(focused) = focused {
tx.send(focused).unwrap();
}
spawn_blocking(move || loop {
while let Ok((_, payload)) = srx.try_recv() {
let payload: WindowEvent = serde_json::from_slice(&payload).unwrap();
let update = match payload.change.as_str() {
"focus" => true,
"title" => payload.container.focused,
_ => false,
};
if update {
tx.send(payload.container).unwrap();
}
}
sway.poll().unwrap();
});
{
rx.attach(None, move |node| {
let value = node
.name
.as_deref()
.unwrap_or_else(|| node.get_id());
let pixbuf = icon::get_icon(&icon_theme, node.get_id(), self.icon_size);
if self.show_icon {
icon.set_pixbuf(pixbuf.as_ref());
}
if self.show_title {
label.set_label(value);
}
Continue(true)
});
}
container
}
}

View file

@ -1,9 +1,9 @@
use crate::collection::Collection; use crate::collection::Collection;
use crate::modules::launcher::icon::{find_desktop_file, get_icon}; use crate::icon::{find_desktop_file, get_icon};
use crate::modules::launcher::node::SwayNode;
use crate::modules::launcher::popup::Popup; use crate::modules::launcher::popup::Popup;
use crate::modules::launcher::FocusEvent; use crate::modules::launcher::FocusEvent;
use crate::popup::PopupAlignment; use crate::popup::PopupAlignment;
use crate::sway::SwayNode;
use gtk::prelude::*; use gtk::prelude::*;
use gtk::{Button, IconTheme, Image}; use gtk::{Button, IconTheme, Image};
use std::process::{Command, Stdio}; use std::process::{Command, Stdio};

View file

@ -1,13 +1,12 @@
mod icon;
mod item; mod item;
mod node;
mod popup; mod popup;
use crate::collection::Collection; use crate::collection::Collection;
use crate::modules::launcher::item::{ButtonConfig, LauncherItem, LauncherWindow}; use crate::modules::launcher::item::{ButtonConfig, LauncherItem, LauncherWindow};
use crate::modules::launcher::node::{get_open_windows, SwayNode};
use crate::modules::launcher::popup::Popup; use crate::modules::launcher::popup::Popup;
use crate::modules::{Module, ModuleInfo}; use crate::modules::{Module, ModuleInfo};
use crate::sway::node::get_open_windows;
use crate::sway::{SwayNode, WindowEvent};
use gtk::prelude::*; use gtk::prelude::*;
use gtk::{IconTheme, Orientation}; use gtk::{IconTheme, Orientation};
use ksway::{Client, IpcEvent}; use ksway::{Client, IpcEvent};
@ -20,28 +19,14 @@ use tokio::task::spawn_blocking;
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize, Clone)]
pub struct LauncherModule { pub struct LauncherModule {
favorites: Option<Vec<String>>, favorites: Option<Vec<String>>,
#[serde(default = "default_false")] #[serde(default = "crate::config::default_false")]
show_names: bool, show_names: bool,
#[serde(default = "default_true")] #[serde(default = "crate::config::default_true")]
show_icons: bool, show_icons: bool,
icon_theme: Option<String>, icon_theme: Option<String>,
} }
const fn default_false() -> bool {
false
}
const fn default_true() -> bool {
true
}
#[derive(Debug, Deserialize)]
struct WindowEvent {
change: String,
container: SwayNode,
}
#[derive(Debug)] #[derive(Debug)]
pub enum FocusEvent { pub enum FocusEvent {
AppId(String), AppId(String),
@ -181,7 +166,6 @@ impl Launcher {
} else { } else {
windows.get_mut(&window.id).unwrap().name = Some(name); windows.get_mut(&window.id).unwrap().name = Some(name);
} }
} }
} }
@ -207,7 +191,12 @@ impl Module<gtk::Box> for LauncherModule {
let mut sway = Client::connect().unwrap(); let mut sway = Client::connect().unwrap();
let popup = Popup::new("popup-launcher", info.app, Orientation::Vertical, info.bar_position); let popup = Popup::new(
"popup-launcher",
info.app,
Orientation::Vertical,
info.bar_position,
);
let container = gtk::Box::new(Orientation::Horizontal, 0); let container = gtk::Box::new(Orientation::Horizontal, 0);
let (ui_tx, mut ui_rx) = mpsc::channel(32); let (ui_tx, mut ui_rx) = mpsc::channel(32);

View file

@ -5,6 +5,7 @@
/// Clicking the widget opens a popup containing the current time /// Clicking the widget opens a popup containing the current time
/// with second-level precision and a calendar. /// with second-level precision and a calendar.
pub mod clock; pub mod clock;
pub mod focused;
pub mod launcher; pub mod launcher;
pub mod mpd; pub mod mpd;
pub mod script; pub mod script;
@ -12,13 +13,13 @@ pub mod sysinfo;
pub mod tray; pub mod tray;
pub mod workspaces; pub mod workspaces;
use crate::config::BarPosition;
/// Shamelessly stolen from here: /// Shamelessly stolen from here:
/// <https://github.com/zeroeightysix/rustbar/blob/master/src/modules/module.rs> /// <https://github.com/zeroeightysix/rustbar/blob/master/src/modules/module.rs>
use glib::IsA; use glib::IsA;
use gtk::{Application, Widget}; use gtk::{Application, Widget};
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use serde_json::Value; use serde_json::Value;
use crate::config::BarPosition;
#[derive(Clone)] #[derive(Clone)]
pub enum ModuleLocation { pub enum ModuleLocation {
@ -31,7 +32,7 @@ pub struct ModuleInfo<'a> {
pub app: &'a Application, pub app: &'a Application,
pub location: ModuleLocation, pub location: ModuleLocation,
pub bar_position: &'a BarPosition, pub bar_position: &'a BarPosition,
pub output_name: &'a str pub output_name: &'a str,
} }
pub trait Module<W> pub trait Module<W>

View file

@ -93,7 +93,12 @@ impl Module<Button> for MpdModule {
let (ui_tx, mut ui_rx) = mpsc::channel(32); let (ui_tx, mut ui_rx) = mpsc::channel(32);
let popup = Popup::new("popup-mpd", info.app, Orientation::Horizontal, info.bar_position); let popup = Popup::new(
"popup-mpd",
info.app,
Orientation::Horizontal,
info.bar_position,
);
let mpd_popup = MpdPopup::new(popup, ui_tx); let mpd_popup = MpdPopup::new(popup, ui_tx);
let (tx, rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT); let (tx, rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
@ -148,11 +153,12 @@ impl Module<Button> for MpdModule {
match status.state { match status.state {
PlayState::Playing => client.command(commands::SetPause(true)).await, PlayState::Playing => client.command(commands::SetPause(true)).await,
PlayState::Paused => client.command(commands::SetPause(false)).await, PlayState::Paused => client.command(commands::SetPause(false)).await,
PlayState::Stopped => Ok(()) PlayState::Stopped => Ok(()),
} }
} }
PopupEvent::Next => client.command(commands::Next).await PopupEvent::Next => client.command(commands::Next).await,
}.unwrap(); }
.unwrap();
} }
}); });

View file

@ -1,4 +1,5 @@
use crate::modules::{Module, ModuleInfo}; use crate::modules::{Module, ModuleInfo};
use crate::sway::{Workspace, WorkspaceEvent};
use gtk::prelude::*; use gtk::prelude::*;
use gtk::{Button, Orientation}; use gtk::{Button, Orientation};
use ksway::client::Client; use ksway::client::Client;
@ -13,22 +14,10 @@ use tokio::task::spawn_blocking;
pub struct WorkspacesModule { pub struct WorkspacesModule {
name_map: Option<HashMap<String, String>>, name_map: Option<HashMap<String, String>>,
#[serde(default = "default_false")] #[serde(default = "crate::config::default_false")]
all_monitors: bool, all_monitors: bool,
} }
const fn default_false() -> bool {
false
}
#[derive(Deserialize, Debug)]
struct Workspace {
name: String,
focused: bool,
// num: i32,
output: String,
}
impl Workspace { impl Workspace {
fn as_button(&self, name_map: &HashMap<String, String>, tx: &mpsc::Sender<String>) -> Button { fn as_button(&self, name_map: &HashMap<String, String>, tx: &mpsc::Sender<String>) -> Button {
let button = Button::builder() let button = Button::builder()
@ -52,13 +41,6 @@ impl Workspace {
} }
} }
#[derive(Deserialize, Debug)]
struct WorkspaceEvent {
change: String,
old: Option<Workspace>,
current: Option<Workspace>,
}
impl Module<gtk::Box> for WorkspacesModule { impl Module<gtk::Box> for WorkspacesModule {
fn into_widget(self, info: &ModuleInfo) -> gtk::Box { fn into_widget(self, info: &ModuleInfo) -> gtk::Box {
let mut sway = Client::connect().unwrap(); let mut sway = Client::connect().unwrap();

49
src/sway/mod.rs Normal file
View file

@ -0,0 +1,49 @@
use serde::Deserialize;
pub mod node;
#[derive(Deserialize, Debug)]
pub struct WorkspaceEvent {
pub change: String,
pub old: Option<Workspace>,
pub current: Option<Workspace>,
}
#[derive(Deserialize, Debug)]
pub struct Workspace {
pub name: String,
pub focused: bool,
// pub num: i32,
pub output: String,
}
#[derive(Debug, Deserialize)]
pub struct WindowEvent {
pub change: String,
pub container: SwayNode,
}
#[derive(Debug, Deserialize)]
pub struct SwayNode {
#[serde(rename = "type")]
pub node_type: String,
pub id: i32,
pub name: Option<String>,
pub app_id: Option<String>,
pub focused: bool,
pub urgent: bool,
pub nodes: Vec<SwayNode>,
pub floating_nodes: Vec<SwayNode>,
pub shell: Option<String>,
pub window_properties: Option<WindowProperties>,
}
#[derive(Debug, Deserialize)]
pub struct WindowProperties {
pub class: String,
}
#[derive(Deserialize)]
pub struct SwayOutput {
pub name: String,
}

View file

@ -1,25 +1,5 @@
use crate::sway::SwayNode;
use ksway::{Client, IpcCommand}; use ksway::{Client, IpcCommand};
use serde::Deserialize;
#[derive(Debug, Deserialize)]
pub struct SwayNode {
#[serde(rename = "type")]
pub node_type: String,
pub id: i32,
pub name: Option<String>,
pub app_id: Option<String>,
pub focused: bool,
pub urgent: bool,
pub nodes: Vec<SwayNode>,
pub floating_nodes: Vec<SwayNode>,
pub shell: Option<String>,
pub window_properties: Option<WindowProperties>,
}
#[derive(Debug, Deserialize)]
pub struct WindowProperties {
pub class: String,
}
impl SwayNode { impl SwayNode {
pub fn get_id(&self) -> &str { pub fn get_id(&self) -> &str {