Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ebf7a97a85 | ||
|
|
7a515359ac | ||
|
|
2e0f033bed | ||
|
|
dc14cb003f | ||
|
|
e416e03b0a | ||
|
|
65c5d391d9 | ||
|
|
53adaa846c | ||
|
|
a358037d3e | ||
|
|
19d009fe5b |
4
.idea/runConfigurations/Clippy.xml
generated
4
.idea/runConfigurations/Clippy.xml
generated
@@ -1,6 +1,6 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
<component name="ProjectRunConfigurationManager">
|
||||||
<configuration default="false" name="Clippy" type="CargoCommandRunConfiguration" factoryName="Cargo Command" nameIsGenerated="true">
|
<configuration default="false" name="Clippy" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
|
||||||
<option name="command" value="clippy -- -W clippy::pedantic -W clippy::nursery -W clippy::unwrap_used -W clippy::expect_used" />
|
<option name="command" value="clippy" />
|
||||||
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
|
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
|
||||||
<option name="channel" value="DEFAULT" />
|
<option name="channel" value="DEFAULT" />
|
||||||
<option name="requiredFeatures" value="false" />
|
<option name="requiredFeatures" value="false" />
|
||||||
|
|||||||
19
.idea/runConfigurations/Clippy__Strict_.xml
generated
Normal file
19
.idea/runConfigurations/Clippy__Strict_.xml
generated
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Clippy (Strict)" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
|
||||||
|
<option name="command" value="clippy -- -W clippy::pedantic -W clippy::nursery -W clippy::unwrap_used -W clippy::expect_used" />
|
||||||
|
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
|
||||||
|
<option name="channel" value="DEFAULT" />
|
||||||
|
<option name="requiredFeatures" value="false" />
|
||||||
|
<option name="allFeatures" value="false" />
|
||||||
|
<option name="emulateTerminal" value="false" />
|
||||||
|
<option name="withSudo" value="false" />
|
||||||
|
<option name="buildTarget" value="REMOTE" />
|
||||||
|
<option name="backtrace" value="SHORT" />
|
||||||
|
<envs />
|
||||||
|
<option name="isRedirectInput" value="false" />
|
||||||
|
<option name="redirectInputPath" value="" />
|
||||||
|
<method v="2">
|
||||||
|
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -1033,7 +1033,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ironbar"
|
name = "ironbar"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"cornfig",
|
"cornfig",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "ironbar"
|
name = "ironbar"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
description = "Customisable wlroots/sway bar"
|
description = "Customisable wlroots/sway bar"
|
||||||
|
|||||||
12
README.md
12
README.md
@@ -21,8 +21,7 @@ Then just run with `ironbar`.
|
|||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
By default, running will get you a blank bar. To start, you will need a configuration file in `.config/ironbar`.
|
By default, running will get you a blank bar. To start, you will need a configuration file in `.config/ironbar`. This could be called `config.<fmt>`, using one of the available extensions:
|
||||||
Ironbar supports a range of file formats so pick your favourite:
|
|
||||||
|
|
||||||
- JSON
|
- JSON
|
||||||
- TOML
|
- TOML
|
||||||
@@ -71,6 +70,15 @@ The monitor's config object takes any combination of `left`, `center`, and `righ
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
| Name | Type | Default | Description |
|
||||||
|
|------------|-------------------|---------|-----------------------------------------------------------------------------|
|
||||||
|
| `position` | `top` or `bottom` | `[]` | The bar's position on screen. |
|
||||||
|
| `height` | `integer` | `42` | The bar's height in pixels. |
|
||||||
|
| `left` | `Module[]` | `[]` | Array of left modules. |
|
||||||
|
| `center` | `Module[]` | `[]` | Array of center modules. |
|
||||||
|
| `right` | `Module[]` | `[]` | Array of right modules. |
|
||||||
|
| `monitors` | `RootConfig[]` | `null` | Array of root config objects for each monitor. Overrides left/center/right. |
|
||||||
|
|
||||||
## Styling
|
## Styling
|
||||||
|
|
||||||
To get started, create a stylesheet at `.config/ironbar/style.css`. Changes will be hot-reloaded every time you save the file.
|
To get started, create a stylesheet at `.config/ironbar/style.css`. Changes will be hot-reloaded every time you save the file.
|
||||||
|
|||||||
36
src/bar.rs
36
src/bar.rs
@@ -1,20 +1,20 @@
|
|||||||
use crate::config::ModuleConfig;
|
use crate::config::{BarPosition, ModuleConfig};
|
||||||
use crate::modules::{Module, ModuleInfo, ModuleLocation};
|
use crate::modules::{Module, ModuleInfo, ModuleLocation};
|
||||||
use crate::Config;
|
use crate::Config;
|
||||||
use gtk::gdk::Monitor;
|
use gtk::gdk::Monitor;
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::{Application, ApplicationWindow, Orientation};
|
use gtk::{Application, ApplicationWindow, Orientation};
|
||||||
|
|
||||||
pub fn create_bar(app: &Application, monitor: &Monitor, config: Config) {
|
pub fn create_bar(app: &Application, monitor: &Monitor, monitor_name: &str, config: Config) {
|
||||||
let win = ApplicationWindow::builder().application(app).build();
|
let win = ApplicationWindow::builder().application(app).build();
|
||||||
|
|
||||||
setup_layer_shell(&win, monitor);
|
setup_layer_shell(&win, monitor, &config.position);
|
||||||
|
|
||||||
let content = gtk::Box::builder()
|
let content = gtk::Box::builder()
|
||||||
.orientation(Orientation::Horizontal)
|
.orientation(Orientation::Horizontal)
|
||||||
.spacing(0)
|
.spacing(0)
|
||||||
.hexpand(false)
|
.hexpand(false)
|
||||||
.height_request(42)
|
.height_request(config.height)
|
||||||
.name("bar")
|
.name("bar")
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@ pub fn create_bar(app: &Application, monitor: &Monitor, config: Config) {
|
|||||||
content.set_center_widget(Some(¢er));
|
content.set_center_widget(Some(¢er));
|
||||||
content.pack_end(&right, false, false, 0);
|
content.pack_end(&right, false, false, 0);
|
||||||
|
|
||||||
load_modules(&left, ¢er, &right, app, config);
|
load_modules(&left, ¢er, &right, app, config, monitor_name);
|
||||||
win.add(&content);
|
win.add(&content);
|
||||||
|
|
||||||
win.connect_destroy_event(|_, _| {
|
win.connect_destroy_event(|_, _| {
|
||||||
@@ -48,11 +48,14 @@ fn load_modules(
|
|||||||
right: >k::Box,
|
right: >k::Box,
|
||||||
app: &Application,
|
app: &Application,
|
||||||
config: Config,
|
config: Config,
|
||||||
|
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,
|
||||||
|
output_name,
|
||||||
};
|
};
|
||||||
|
|
||||||
add_modules(left, modules, info);
|
add_modules(left, modules, info);
|
||||||
@@ -62,6 +65,8 @@ fn load_modules(
|
|||||||
let info = ModuleInfo {
|
let info = ModuleInfo {
|
||||||
app,
|
app,
|
||||||
location: ModuleLocation::Center,
|
location: ModuleLocation::Center,
|
||||||
|
bar_position: &config.position,
|
||||||
|
output_name,
|
||||||
};
|
};
|
||||||
|
|
||||||
add_modules(center, modules, info);
|
add_modules(center, modules, info);
|
||||||
@@ -71,6 +76,8 @@ fn load_modules(
|
|||||||
let info = ModuleInfo {
|
let info = ModuleInfo {
|
||||||
app,
|
app,
|
||||||
location: ModuleLocation::Right,
|
location: ModuleLocation::Right,
|
||||||
|
bar_position: &config.position,
|
||||||
|
output_name,
|
||||||
};
|
};
|
||||||
|
|
||||||
add_modules(right, modules, info);
|
add_modules(right, modules, info);
|
||||||
@@ -115,11 +122,16 @@ fn add_modules(content: >k::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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_layer_shell(win: &ApplicationWindow, monitor: &Monitor) {
|
fn setup_layer_shell(win: &ApplicationWindow, monitor: &Monitor, position: &BarPosition) {
|
||||||
gtk_layer_shell::init_for_window(win);
|
gtk_layer_shell::init_for_window(win);
|
||||||
gtk_layer_shell::set_monitor(win, monitor);
|
gtk_layer_shell::set_monitor(win, monitor);
|
||||||
gtk_layer_shell::set_layer(win, gtk_layer_shell::Layer::Top);
|
gtk_layer_shell::set_layer(win, gtk_layer_shell::Layer::Top);
|
||||||
@@ -130,8 +142,16 @@ fn setup_layer_shell(win: &ApplicationWindow, monitor: &Monitor) {
|
|||||||
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, false);
|
gtk_layer_shell::set_anchor(
|
||||||
gtk_layer_shell::set_anchor(win, gtk_layer_shell::Edge::Bottom, true);
|
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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,10 +20,29 @@ pub enum ModuleConfig {
|
|||||||
SysInfo(SysInfoModule),
|
SysInfo(SysInfoModule),
|
||||||
Launcher(LauncherModule),
|
Launcher(LauncherModule),
|
||||||
Script(ScriptModule),
|
Script(ScriptModule),
|
||||||
|
Focused(FocusedModule),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Clone, PartialEq, Eq)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
pub enum BarPosition {
|
||||||
|
Top,
|
||||||
|
Bottom,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for BarPosition {
|
||||||
|
fn default() -> Self {
|
||||||
|
BarPosition::Bottom
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Clone, Default)]
|
#[derive(Debug, Deserialize, Clone, Default)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
|
#[serde(default = "default_bar_position")]
|
||||||
|
pub position: BarPosition,
|
||||||
|
#[serde(default = "default_bar_height")]
|
||||||
|
pub height: i32,
|
||||||
|
|
||||||
pub left: Option<Vec<ModuleConfig>>,
|
pub left: Option<Vec<ModuleConfig>>,
|
||||||
pub center: Option<Vec<ModuleConfig>>,
|
pub center: Option<Vec<ModuleConfig>>,
|
||||||
pub right: Option<Vec<ModuleConfig>>,
|
pub right: Option<Vec<ModuleConfig>>,
|
||||||
@@ -30,6 +50,14 @@ pub struct Config {
|
|||||||
pub monitors: Option<Vec<Config>>,
|
pub monitors: Option<Vec<Config>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fn default_bar_position() -> BarPosition {
|
||||||
|
BarPosition::Bottom
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn default_bar_height() -> i32 {
|
||||||
|
42
|
||||||
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
pub fn load() -> Option<Self> {
|
pub fn load() -> Option<Self> {
|
||||||
let config_dir = config_dir().expect("Failed to locate user config dir");
|
let config_dir = config_dir().expect("Failed to locate user config dir");
|
||||||
@@ -63,3 +91,10 @@ impl Config {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const fn default_false() -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
pub const fn default_true() -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ pub fn get_icon(theme: &IconTheme, app_id: &str, size: i32) -> Option<Pixbuf> {
|
|||||||
|
|
||||||
match icon_location {
|
match icon_location {
|
||||||
Some(IconLocation::Theme(icon_name)) => {
|
Some(IconLocation::Theme(icon_name)) => {
|
||||||
let icon = theme.load_icon(&icon_name, size, IconLookupFlags::empty());
|
let icon = theme.load_icon(&icon_name, size, IconLookupFlags::FORCE_SIZE);
|
||||||
|
|
||||||
match icon {
|
match icon {
|
||||||
Ok(icon) => icon,
|
Ok(icon) => icon,
|
||||||
20
src/main.rs
20
src/main.rs
@@ -1,16 +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::IpcCommand;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
@@ -18,7 +23,14 @@ async fn main() {
|
|||||||
.application_id("dev.jstanger.waylandbar")
|
.application_id("dev.jstanger.waylandbar")
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
app.connect_activate(|app| {
|
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 = serde_json::from_slice::<Vec<SwayOutput>>(&outputs)
|
||||||
|
.expect("Failed to deserialize outputs message from Sway IPC");
|
||||||
|
|
||||||
|
app.connect_activate(move |app| {
|
||||||
let config = Config::load().unwrap_or_default();
|
let config = Config::load().unwrap_or_default();
|
||||||
|
|
||||||
// TODO: Better logging (https://crates.io/crates/tracing)
|
// TODO: Better logging (https://crates.io/crates/tracing)
|
||||||
@@ -30,12 +42,16 @@ 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 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)
|
||||||
});
|
});
|
||||||
|
|
||||||
create_bar(app, &monitor, config.clone());
|
create_bar(app, &monitor, monitor_name, config.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
let style_path = config_dir()
|
let style_path = config_dir()
|
||||||
|
|||||||
@@ -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);
|
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
97
src/modules/focused.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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};
|
||||||
|
|||||||
@@ -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);
|
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);
|
||||||
|
|||||||
@@ -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,6 +13,7 @@ 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;
|
||||||
@@ -29,6 +31,8 @@ pub enum ModuleLocation {
|
|||||||
pub struct ModuleInfo<'a> {
|
pub struct ModuleInfo<'a> {
|
||||||
pub app: &'a Application,
|
pub app: &'a Application,
|
||||||
pub location: ModuleLocation,
|
pub location: ModuleLocation,
|
||||||
|
pub bar_position: &'a BarPosition,
|
||||||
|
pub output_name: &'a str,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Module<W>
|
pub trait Module<W>
|
||||||
|
|||||||
@@ -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);
|
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();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ const fn default_interval() -> u64 {
|
|||||||
|
|
||||||
impl Module<Label> for ScriptModule {
|
impl Module<Label> for ScriptModule {
|
||||||
fn into_widget(self, _info: &ModuleInfo) -> Label {
|
fn into_widget(self, _info: &ModuleInfo) -> Label {
|
||||||
let label = Label::new(None);
|
let label = Label::builder().use_markup(true).build();
|
||||||
|
|
||||||
let (tx, rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
|
let (tx, rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
|
||||||
spawn(async move {
|
spawn(async move {
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -11,15 +12,10 @@ use tokio::task::spawn_blocking;
|
|||||||
|
|
||||||
#[derive(Debug, Deserialize, Clone)]
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
pub struct WorkspacesModule {
|
pub struct WorkspacesModule {
|
||||||
pub(crate) name_map: Option<HashMap<String, String>>,
|
name_map: Option<HashMap<String, String>>,
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[serde(default = "crate::config::default_false")]
|
||||||
struct Workspace {
|
all_monitors: bool,
|
||||||
name: String,
|
|
||||||
focused: bool,
|
|
||||||
// num: i32,
|
|
||||||
// output: String,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Workspace {
|
impl Workspace {
|
||||||
@@ -45,22 +41,24 @@ 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();
|
||||||
|
|
||||||
let container = gtk::Box::new(Orientation::Horizontal, 0);
|
let container = gtk::Box::new(Orientation::Horizontal, 0);
|
||||||
|
|
||||||
let workspaces = {
|
let workspaces = {
|
||||||
let raw = sway.ipc(IpcCommand::GetWorkspaces).unwrap();
|
let raw = sway.ipc(IpcCommand::GetWorkspaces).unwrap();
|
||||||
serde_json::from_slice::<Vec<Workspace>>(&raw).unwrap()
|
let workspaces = serde_json::from_slice::<Vec<Workspace>>(&raw).unwrap();
|
||||||
|
|
||||||
|
if !self.all_monitors {
|
||||||
|
workspaces
|
||||||
|
.into_iter()
|
||||||
|
.filter(|workspace| workspace.output == info.output_name)
|
||||||
|
.collect()
|
||||||
|
} else {
|
||||||
|
workspaces
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let name_map = self.name_map.unwrap_or_default();
|
let name_map = self.name_map.unwrap_or_default();
|
||||||
@@ -88,29 +86,35 @@ impl Module<gtk::Box> for WorkspacesModule {
|
|||||||
|
|
||||||
{
|
{
|
||||||
let menubar = container.clone();
|
let menubar = container.clone();
|
||||||
|
let output_name = info.output_name.to_string();
|
||||||
rx.attach(None, move |event| {
|
rx.attach(None, move |event| {
|
||||||
match event.change.as_str() {
|
match event.change.as_str() {
|
||||||
"focus" => {
|
"focus" => {
|
||||||
let old = event.old.unwrap();
|
let old = event.old.unwrap();
|
||||||
let old_button = button_map.get(&old.name).unwrap();
|
if let Some(old_button) = button_map.get(&old.name) {
|
||||||
old_button.style_context().remove_class("focused");
|
old_button.style_context().remove_class("focused");
|
||||||
|
}
|
||||||
|
|
||||||
let new = event.current.unwrap();
|
let new = event.current.unwrap();
|
||||||
let new_button = button_map.get(&new.name).unwrap();
|
if let Some(new_button) = button_map.get(&new.name) {
|
||||||
new_button.style_context().add_class("focused");
|
new_button.style_context().add_class("focused");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
"init" => {
|
"init" => {
|
||||||
let workspace = event.current.unwrap();
|
let workspace = event.current.unwrap();
|
||||||
let item = workspace.as_button(&name_map, &ui_tx);
|
if self.all_monitors || workspace.output == output_name {
|
||||||
|
let item = workspace.as_button(&name_map, &ui_tx);
|
||||||
|
|
||||||
item.show();
|
item.show();
|
||||||
menubar.add(&item);
|
menubar.add(&item);
|
||||||
button_map.insert(workspace.name, item);
|
button_map.insert(workspace.name, item);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
"empty" => {
|
"empty" => {
|
||||||
let current = event.current.unwrap();
|
let current = event.current.unwrap();
|
||||||
let item = button_map.get(¤t.name).unwrap();
|
if let Some(item) = button_map.get(¤t.name) {
|
||||||
menubar.remove(item);
|
menubar.remove(item);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|||||||
40
src/popup.rs
40
src/popup.rs
@@ -1,3 +1,4 @@
|
|||||||
|
use crate::config::BarPosition;
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::{Application, ApplicationWindow, Orientation};
|
use gtk::{Application, ApplicationWindow, Orientation};
|
||||||
|
|
||||||
@@ -14,19 +15,48 @@ pub enum PopupAlignment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Popup {
|
impl Popup {
|
||||||
pub fn new(name: &str, app: &Application, orientation: Orientation) -> Self {
|
pub fn new(
|
||||||
|
name: &str,
|
||||||
|
app: &Application,
|
||||||
|
orientation: Orientation,
|
||||||
|
bar_position: &BarPosition,
|
||||||
|
) -> Self {
|
||||||
let win = ApplicationWindow::builder().application(app).build();
|
let win = ApplicationWindow::builder().application(app).build();
|
||||||
|
|
||||||
gtk_layer_shell::init_for_window(&win);
|
gtk_layer_shell::init_for_window(&win);
|
||||||
gtk_layer_shell::set_layer(&win, gtk_layer_shell::Layer::Overlay);
|
gtk_layer_shell::set_layer(&win, gtk_layer_shell::Layer::Overlay);
|
||||||
|
|
||||||
gtk_layer_shell::set_margin(&win, gtk_layer_shell::Edge::Top, 0);
|
gtk_layer_shell::set_margin(
|
||||||
gtk_layer_shell::set_margin(&win, gtk_layer_shell::Edge::Bottom, 5);
|
&win,
|
||||||
|
gtk_layer_shell::Edge::Top,
|
||||||
|
if bar_position == &BarPosition::Top {
|
||||||
|
5
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
},
|
||||||
|
);
|
||||||
|
gtk_layer_shell::set_margin(
|
||||||
|
&win,
|
||||||
|
gtk_layer_shell::Edge::Bottom,
|
||||||
|
if bar_position == &BarPosition::Bottom {
|
||||||
|
5
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
},
|
||||||
|
);
|
||||||
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, false);
|
gtk_layer_shell::set_anchor(
|
||||||
gtk_layer_shell::set_anchor(&win, gtk_layer_shell::Edge::Bottom, true);
|
&win,
|
||||||
|
gtk_layer_shell::Edge::Top,
|
||||||
|
bar_position == &BarPosition::Top,
|
||||||
|
);
|
||||||
|
gtk_layer_shell::set_anchor(
|
||||||
|
&win,
|
||||||
|
gtk_layer_shell::Edge::Bottom,
|
||||||
|
bar_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, false);
|
gtk_layer_shell::set_anchor(&win, gtk_layer_shell::Edge::Right, false);
|
||||||
|
|
||||||
|
|||||||
49
src/sway/mod.rs
Normal file
49
src/sway/mod.rs
Normal 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,
|
||||||
|
}
|
||||||
@@ -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 {
|
||||||
Reference in New Issue
Block a user