mirror of
https://github.com/JakeStanger/ironbar.git
synced 2024-11-22 14:04:20 +03:00
Merge branch 'JakeStanger:master' into fix-nix-pixbuf-loader
This commit is contained in:
commit
0382b50cf4
@ -289,9 +289,10 @@ For details on available modules and each of their config options, check the sid
|
||||
|
||||
For information on the `Script` type, and embedding scripts in strings, see [here](script).
|
||||
|
||||
#### Events
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|-------------------|--------------------|---------|--------------------------------------------------------------------------------------------------------------------|
|
||||
| `show_if` | `Script [polling]` | `null` | Polls the script to check its exit code. If exit code is zero, the module is shown. For other codes, it is hidden. |
|
||||
| `on_click_left` | `Script [oneshot]` | `null` | Runs the script when the module is left clicked. |
|
||||
| `on_click_middle` | `Script [oneshot]` | `null` | Runs the script when the module is middle clicked. |
|
||||
| `on_click_right` | `Script [oneshot]` | `null` | Runs the script when the module is right clicked. |
|
||||
@ -299,4 +300,19 @@ For information on the `Script` type, and embedding scripts in strings, see [her
|
||||
| `on_scroll_down` | `Script [oneshot]` | `null` | Runs the script when the module is scrolled down on. |
|
||||
| `on_mouse_enter` | `Script [oneshot]` | `null` | Runs the script when the module is hovered over. |
|
||||
| `on_mouse_exit` | `Script [oneshot]` | `null` | Runs the script when the module is no longer hovered over. |
|
||||
| `tooltip` | `string` | `null` | Shows this text on hover. Supports embedding scripts between `{{double braces}}`. |
|
||||
|
||||
#### Visibility
|
||||
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|-----------------------|-------------------------------------------------------|---------------|--------------------------------------------------------------------------------------------------------------------|
|
||||
| `show_if` | `Script [polling]` | `null` | Polls the script to check its exit code. If exit code is zero, the module is shown. For other codes, it is hidden. |
|
||||
| `transition_type` | `slide_start` or `slide_end` or `crossfade` or `none` | `slide_start` | The transition animation to use when showing/hiding the widget. |
|
||||
| `transition_duration` | `Integer` | `250` | The length of the transition animation to use when showing/hiding the widget. |
|
||||
|
||||
#### Other
|
||||
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|-------------------|--------------------|---------|--------------------------------------------------------------------------------------------------------------------|
|
||||
| `tooltip` | `string` | `null` | Shows this text on hover. Supports embedding scripts between `{{double braces}}`. |
|
@ -12,6 +12,7 @@ Supports plain text and images.
|
||||
| Name | Type | Default | Description |
|
||||
|-----------------------|---------------------------------------|---------|-------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `icon` | `string/image` | `` | Icon to show on the widget button. |
|
||||
| `icon_size` | `integer` | `32` | Size to render icon at (image icons only). |
|
||||
| `max_items` | `integer` | `10` | Maximum number of items to show in the popup. |
|
||||
| `truncate` | `start` or `middle` or `end` or `Map` | `null` | The location of the ellipses and where to truncate text from. Leave null to avoid truncating. Use the long-hand `Map` version if specifying a length. |
|
||||
| `truncate.mode` | `start` or `middle` or `end` | `null` | The location of the ellipses and where to truncate text from. Leave null to avoid truncating. |
|
||||
|
@ -15,7 +15,8 @@ It is well worth looking at the examples.
|
||||
There are many widget types, each with their own config options.
|
||||
You can think of these like HTML elements and their attributes.
|
||||
|
||||
Every widget has the following options available; `type` is mandatory.
|
||||
Every widget has the following options available; `type` is mandatory.
|
||||
You can also add common [module-level options](https://github.com/JakeStanger/ironbar/wiki/configuration-guide#32-module-level-options) on a widget.
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|---------|-------------------------------------------------------------------|---------|-------------------------------|
|
||||
@ -75,16 +76,17 @@ A draggable slider.
|
||||
Note that `on_change` will provide the **floating point** value as an argument.
|
||||
If your input program requires an integer, you will need to round it.
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|---------------|----------------------------------------------------|--------------|------------------------------------------------------------------------------|
|
||||
| `src` | `image` | `null` | Image source. See [here](images) for information on images. |
|
||||
| `size` | `integer` | `null` | Width/height of the image. Aspect ratio is preserved. |
|
||||
| `orientation` | `horizontal` or `vertical` (shorthand: `h` or `v`) | `horizontal` | Orientation of the slider. |
|
||||
| `value` | `Script` | `null` | Script to run to get the slider value. Output must be a valid number. |
|
||||
| `on_change` | `string [command]` | `null` | Command to execute when the slider changes. More on this [below](#commands). |
|
||||
| `min` | `float` | `0` | Minimum slider value. |
|
||||
| `max` | `float` | `100` | Maximum slider value. |
|
||||
| `length` | `integer` | `null` | Slider length. GTK will automatically size if left unset. |
|
||||
| Name | Type | Default | Description |
|
||||
|---------------|----------------------------------------------------|--------------|---------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `src` | `image` | `null` | Image source. See [here](images) for information on images. |
|
||||
| `size` | `integer` | `null` | Width/height of the image. Aspect ratio is preserved. |
|
||||
| `orientation` | `horizontal` or `vertical` (shorthand: `h` or `v`) | `horizontal` | Orientation of the slider. |
|
||||
| `value` | `Script` | `null` | Script to run to get the slider value. Output must be a valid number. |
|
||||
| `on_change` | `string [command]` | `null` | Command to execute when the slider changes. More on this [below](#commands). |
|
||||
| `min` | `float` | `0` | Minimum slider value. |
|
||||
| `max` | `float` | `100` | Maximum slider value. |
|
||||
| `step` | `float` | - | The increment to change when scrolling with the mouse wheel. If left blank, will use the default determined by the environment. |
|
||||
| `length` | `integer` | `null` | Slider length. GTK will automatically size if left unset. |
|
||||
|
||||
The example slider widget below shows a volume control for MPC,
|
||||
which updates the server when changed, and polls the server for volume changes to keep the slider in sync.
|
||||
|
@ -14,6 +14,7 @@ Optionally displays a launchable set of favourites.
|
||||
| `favorites` | `string[]` | `[]` | List of app IDs (or classes) to always show at the start of the launcher |
|
||||
| `show_names` | `boolean` | `false` | Whether to show app names on the button label. Names will still show on tooltips when set to false. |
|
||||
| `show_icons` | `boolean` | `true` | Whether to show app icons on the button. |
|
||||
| `icon_size` | `integer` | `32` | Size to render icon at (image icons only). |
|
||||
|
||||
<details>
|
||||
<summary>JSON</summary>
|
||||
|
@ -27,6 +27,8 @@ in MPRIS mode, the widget will listen to all players and automatically detect/di
|
||||
| `icons.track` | `string/image` | `` | Icon to show next to track title. |
|
||||
| `icons.album` | `string/image` | `` | Icon to show next to album name. |
|
||||
| `icons.artist` | `string/image` | `ﴁ` | Icon to show next to artist name. |
|
||||
| `icon_size` | `integer` | `32` | Size to render icon at (image icons only). |
|
||||
| `cover_image_size` | `integer` | `128` | Size to render album art image at inside popup. |
|
||||
| `host` | `string/image` | `localhost:6600` | [MPD Only] TCP or Unix socket for the MPD server. |
|
||||
| `music_dir` | `string/image` | `$HOME/Music` | [MPD Only] Path to MPD server's music directory on disc. Required for album art. |
|
||||
|
||||
|
@ -11,6 +11,7 @@ Shows all current workspaces. Clicking a workspace changes focus to it.
|
||||
| Name | Type | Default | Description |
|
||||
|----------------|-----------------------------|----------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `name_map` | `Map<string, string/image>` | `{}` | A map of actual workspace names to their display labels/images. Workspaces use their actual name if not present in the map. See [here](images) for information on images. |
|
||||
| `icon_size` | `integer` | `32` | Size to render icon at (image icons only). |
|
||||
| `all_monitors` | `boolean` | `false` | Whether to display workspaces from all monitors. When `false`, only shows workspaces on the current monitor. |
|
||||
| `sort` | `added` or `alphanumeric` | `alphanumeric` | The method used for sorting workspaces. `added` always appends to the end, `alphanumeric` sorts by number/name. |
|
||||
|
||||
|
12
flake.nix
12
flake.nix
@ -57,6 +57,18 @@
|
||||
default = self.packages.${system}.ironbar;
|
||||
}
|
||||
);
|
||||
apps = genSystems (system: let
|
||||
pkgs = pkgsFor system;
|
||||
in {
|
||||
default = {
|
||||
type = "app";
|
||||
program = "${pkgs.ironbar}/bin/ironbar";
|
||||
};
|
||||
ironbar = {
|
||||
type = "app";
|
||||
program = "${pkgs.ironbar}/bin/ironbar";
|
||||
};
|
||||
});
|
||||
devShells = genSystems (system: let
|
||||
pkgs = pkgsFor system;
|
||||
rust = mkRustToolchain pkgs;
|
||||
|
@ -59,6 +59,6 @@ rustPlatform.buildRustPackage rec {
|
||||
description = "Customisable gtk-layer-shell wlroots/sway bar written in rust.";
|
||||
license = licenses.mit;
|
||||
platforms = platforms.linux;
|
||||
mainProgram = "Hyprland";
|
||||
mainProgram = "ironbar";
|
||||
};
|
||||
}
|
||||
|
@ -190,11 +190,13 @@ fn add_modules(
|
||||
let popup = Popup::new(info, popup_gap);
|
||||
let popup = Arc::new(RwLock::new(popup));
|
||||
|
||||
let orientation = info.bar_position.get_orientation();
|
||||
|
||||
macro_rules! add_module {
|
||||
($module:expr, $id:expr) => {{
|
||||
let common = $module.common.take().expect("Common config did not exist");
|
||||
let widget = create_module(*$module, $id, &info, &Arc::clone(&popup))?;
|
||||
let container = wrap_widget(&widget, common);
|
||||
let container = wrap_widget(&widget, common, orientation);
|
||||
content.add(&container);
|
||||
}};
|
||||
}
|
||||
|
@ -59,8 +59,8 @@ impl ClipboardClient {
|
||||
let iter = senders.iter();
|
||||
for (tx, sender_cache_size) in iter {
|
||||
if cache_size == *sender_cache_size {
|
||||
let mut cache = lock!(cache);
|
||||
let removed_id = cache
|
||||
// let mut cache = lock!(cache);
|
||||
let removed_id = lock!(cache)
|
||||
.remove_ref_first()
|
||||
.expect("Clipboard cache unexpectedly empty");
|
||||
try_send!(tx, ClipboardEvent::Remove(removed_id));
|
||||
@ -131,8 +131,7 @@ impl ClipboardClient {
|
||||
}
|
||||
|
||||
pub fn remove(&self, id: usize) {
|
||||
let mut cache = lock!(self.cache);
|
||||
cache.remove(id);
|
||||
lock!(self.cache).remove(id);
|
||||
|
||||
let senders = lock!(self.senders);
|
||||
let iter = senders.iter();
|
||||
|
@ -3,7 +3,7 @@ use crate::script::{Script, ScriptInput};
|
||||
use crate::send;
|
||||
use gtk::gdk::ScrollDirection;
|
||||
use gtk::prelude::*;
|
||||
use gtk::EventBox;
|
||||
use gtk::{EventBox, Orientation, Revealer, RevealerTransitionType};
|
||||
use serde::Deserialize;
|
||||
use tokio::spawn;
|
||||
use tracing::trace;
|
||||
@ -13,6 +13,8 @@ use tracing::trace;
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub struct CommonConfig {
|
||||
pub show_if: Option<ScriptInput>,
|
||||
pub transition_type: Option<TransitionType>,
|
||||
pub transition_duration: Option<u32>,
|
||||
|
||||
pub on_click_left: Option<ScriptInput>,
|
||||
pub on_click_right: Option<ScriptInput>,
|
||||
@ -25,10 +27,36 @@ pub struct CommonConfig {
|
||||
pub tooltip: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum TransitionType {
|
||||
None,
|
||||
Crossfade,
|
||||
SlideStart,
|
||||
SlideEnd,
|
||||
}
|
||||
|
||||
impl TransitionType {
|
||||
pub fn to_revealer_transition_type(&self, orientation: Orientation) -> RevealerTransitionType {
|
||||
match (self, orientation) {
|
||||
(TransitionType::SlideStart, Orientation::Horizontal) => {
|
||||
RevealerTransitionType::SlideLeft
|
||||
}
|
||||
(TransitionType::SlideStart, Orientation::Vertical) => RevealerTransitionType::SlideUp,
|
||||
(TransitionType::SlideEnd, Orientation::Horizontal) => {
|
||||
RevealerTransitionType::SlideRight
|
||||
}
|
||||
(TransitionType::SlideEnd, Orientation::Vertical) => RevealerTransitionType::SlideDown,
|
||||
(TransitionType::Crossfade, _) => RevealerTransitionType::Crossfade,
|
||||
_ => RevealerTransitionType::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CommonConfig {
|
||||
/// Configures the module's container according to the common config options.
|
||||
pub fn install(mut self, container: &EventBox) {
|
||||
self.install_show_if(container);
|
||||
pub fn install(mut self, container: &EventBox, revealer: &Revealer) {
|
||||
self.install_show_if(container, revealer);
|
||||
|
||||
let left_click_script = self.on_click_left.map(Script::new_polling);
|
||||
let middle_click_script = self.on_click_middle.map(Script::new_polling);
|
||||
@ -91,7 +119,7 @@ impl CommonConfig {
|
||||
}
|
||||
}
|
||||
|
||||
fn install_show_if(&mut self, container: &EventBox) {
|
||||
fn install_show_if(&mut self, container: &EventBox, revealer: &Revealer) {
|
||||
self.show_if.take().map_or_else(
|
||||
|| {
|
||||
container.show_all();
|
||||
@ -100,6 +128,7 @@ impl CommonConfig {
|
||||
let script = Script::new_polling(show_if);
|
||||
let container = container.clone();
|
||||
let (tx, rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
|
||||
|
||||
spawn(async move {
|
||||
script
|
||||
.run(None, |_, success| {
|
||||
@ -107,13 +136,24 @@ impl CommonConfig {
|
||||
})
|
||||
.await;
|
||||
});
|
||||
rx.attach(None, move |success| {
|
||||
if success {
|
||||
container.show_all();
|
||||
} else {
|
||||
container.hide();
|
||||
};
|
||||
Continue(true)
|
||||
|
||||
{
|
||||
let revealer = revealer.clone();
|
||||
let container = container.clone();
|
||||
|
||||
rx.attach(None, move |success| {
|
||||
if success {
|
||||
container.show_all();
|
||||
}
|
||||
revealer.set_reveal_child(success);
|
||||
Continue(true)
|
||||
});
|
||||
}
|
||||
|
||||
revealer.connect_child_revealed_notify(move |revealer| {
|
||||
if !revealer.reveals_child() {
|
||||
container.hide()
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
|
@ -22,7 +22,7 @@ use crate::modules::workspaces::WorkspacesModule;
|
||||
use serde::Deserialize;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub use self::common::CommonConfig;
|
||||
pub use self::common::{CommonConfig, TransitionType};
|
||||
pub use self::truncate::{EllipsizeMode, TruncateMode};
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
|
@ -88,9 +88,13 @@ impl DynamicString {
|
||||
|
||||
let mut chars = input.chars().collect::<Vec<_>>();
|
||||
while !chars.is_empty() {
|
||||
let char_pair = &chars[..=1];
|
||||
let char_pair = if chars.len() > 1 {
|
||||
Some(&chars[..=1])
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let (token, skip) = if let ['{', '{'] = char_pair {
|
||||
let (token, skip) = if let Some(['{', '{']) = char_pair {
|
||||
const SKIP_BRACKETS: usize = 4; // two braces either side
|
||||
|
||||
let str = chars
|
||||
|
@ -60,10 +60,10 @@ async fn main() -> Result<()> {
|
||||
|display| display,
|
||||
);
|
||||
|
||||
let config_res = match env::var("IRONBAR_CONFIG") {
|
||||
Ok(path) => ConfigLoader::load(path),
|
||||
Err(_) => ConfigLoader::new("ironbar").find_and_load(),
|
||||
};
|
||||
let config_res = env::var("IRONBAR_CONFIG").map_or_else(
|
||||
|_| ConfigLoader::new("ironbar").find_and_load(),
|
||||
ConfigLoader::load,
|
||||
);
|
||||
|
||||
let config = match config_res {
|
||||
Ok(config) => config,
|
||||
|
@ -21,6 +21,9 @@ pub struct ClipboardModule {
|
||||
#[serde(default = "default_icon")]
|
||||
icon: String,
|
||||
|
||||
#[serde(default = "default_icon_size")]
|
||||
icon_size: i32,
|
||||
|
||||
#[serde(default = "default_max_items")]
|
||||
max_items: usize,
|
||||
|
||||
@ -35,6 +38,10 @@ fn default_icon() -> String {
|
||||
String::from("")
|
||||
}
|
||||
|
||||
const fn default_icon_size() -> i32 {
|
||||
32
|
||||
}
|
||||
|
||||
const fn default_max_items() -> usize {
|
||||
10
|
||||
}
|
||||
@ -120,7 +127,7 @@ impl Module<Button> for ClipboardModule {
|
||||
) -> color_eyre::Result<ModuleWidget<Button>> {
|
||||
let position = info.bar_position;
|
||||
|
||||
let button = new_icon_button(&self.icon, info.icon_theme, 32);
|
||||
let button = new_icon_button(&self.icon, info.icon_theme, self.icon_size);
|
||||
button.style_context().add_class("btn");
|
||||
|
||||
button.connect_clicked(move |button| {
|
||||
|
@ -1,5 +1,6 @@
|
||||
use super::{try_get_orientation, CustomWidget, CustomWidgetContext, Widget};
|
||||
use super::{try_get_orientation, CustomWidget, CustomWidgetContext};
|
||||
use crate::build;
|
||||
use crate::modules::custom::WidgetConfig;
|
||||
use gtk::prelude::*;
|
||||
use gtk::Orientation;
|
||||
use serde::Deserialize;
|
||||
@ -9,7 +10,7 @@ pub struct BoxWidget {
|
||||
name: Option<String>,
|
||||
class: Option<String>,
|
||||
orientation: Option<String>,
|
||||
widgets: Option<Vec<Widget>>,
|
||||
widgets: Option<Vec<WidgetConfig>>,
|
||||
}
|
||||
|
||||
impl CustomWidget for BoxWidget {
|
||||
@ -26,7 +27,7 @@ impl CustomWidget for BoxWidget {
|
||||
|
||||
if let Some(widgets) = self.widgets {
|
||||
for widget in widgets {
|
||||
widget.add_to(&container, context);
|
||||
widget.widget.add_to(&container, context, widget.common);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -12,7 +12,9 @@ use self::slider::SliderWidget;
|
||||
use crate::config::CommonConfig;
|
||||
use crate::modules::custom::button::ButtonWidget;
|
||||
use crate::modules::custom::progress::ProgressWidget;
|
||||
use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext};
|
||||
use crate::modules::{
|
||||
wrap_widget, Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext,
|
||||
};
|
||||
use crate::popup::WidgetGeometry;
|
||||
use crate::script::Script;
|
||||
use crate::send_async;
|
||||
@ -29,14 +31,22 @@ pub struct CustomModule {
|
||||
/// Container class name
|
||||
class: Option<String>,
|
||||
/// Widgets to add to the bar container
|
||||
bar: Vec<Widget>,
|
||||
bar: Vec<WidgetConfig>,
|
||||
/// Widgets to add to the popup container
|
||||
popup: Option<Vec<Widget>>,
|
||||
popup: Option<Vec<WidgetConfig>>,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub common: Option<CommonConfig>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub struct WidgetConfig {
|
||||
#[serde(flatten)]
|
||||
widget: Widget,
|
||||
#[serde(flatten)]
|
||||
common: CommonConfig,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
pub enum Widget {
|
||||
@ -107,15 +117,27 @@ fn try_get_orientation(orientation: &str) -> Result<Orientation> {
|
||||
|
||||
impl Widget {
|
||||
/// Creates this widget and adds it to the parent container
|
||||
fn add_to(self, parent: >k::Box, context: CustomWidgetContext) {
|
||||
match self {
|
||||
Self::Box(widget) => parent.add(&widget.into_widget(context)),
|
||||
Self::Label(widget) => parent.add(&widget.into_widget(context)),
|
||||
Self::Button(widget) => parent.add(&widget.into_widget(context)),
|
||||
Self::Image(widget) => parent.add(&widget.into_widget(context)),
|
||||
Self::Slider(widget) => parent.add(&widget.into_widget(context)),
|
||||
Self::Progress(widget) => parent.add(&widget.into_widget(context)),
|
||||
fn add_to(self, parent: >k::Box, context: CustomWidgetContext, common: CommonConfig) {
|
||||
macro_rules! create {
|
||||
($widget:expr) => {
|
||||
wrap_widget(
|
||||
&$widget.into_widget(context),
|
||||
common,
|
||||
context.bar_orientation,
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
let event_box = match self {
|
||||
Self::Box(widget) => create!(widget),
|
||||
Self::Label(widget) => create!(widget),
|
||||
Self::Button(widget) => create!(widget),
|
||||
Self::Image(widget) => create!(widget),
|
||||
Self::Slider(widget) => create!(widget),
|
||||
Self::Progress(widget) => create!(widget),
|
||||
};
|
||||
|
||||
parent.add(&event_box);
|
||||
}
|
||||
}
|
||||
|
||||
@ -186,7 +208,9 @@ impl Module<gtk::Box> for CustomModule {
|
||||
};
|
||||
|
||||
self.bar.clone().into_iter().for_each(|widget| {
|
||||
widget.add_to(&container, custom_context);
|
||||
widget
|
||||
.widget
|
||||
.add_to(&container, custom_context, widget.common);
|
||||
});
|
||||
|
||||
let popup = self.into_popup(context.controller_tx, context.popup_rx, info);
|
||||
@ -222,7 +246,9 @@ impl Module<gtk::Box> for CustomModule {
|
||||
};
|
||||
|
||||
for widget in popup {
|
||||
widget.add_to(&container, custom_context);
|
||||
widget
|
||||
.widget
|
||||
.add_to(&container, custom_context, widget.common);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,7 @@ use gtk::prelude::*;
|
||||
use gtk::Scale;
|
||||
use serde::Deserialize;
|
||||
use std::cell::Cell;
|
||||
use std::ops::Neg;
|
||||
use tokio::spawn;
|
||||
use tracing::error;
|
||||
|
||||
@ -21,6 +22,7 @@ pub struct SliderWidget {
|
||||
min: f64,
|
||||
#[serde(default = "default_max")]
|
||||
max: f64,
|
||||
step: Option<f64>,
|
||||
length: Option<i32>,
|
||||
}
|
||||
|
||||
@ -53,11 +55,26 @@ impl CustomWidget for SliderWidget {
|
||||
if let Some(on_change) = self.on_change {
|
||||
let min = self.min;
|
||||
let max = self.max;
|
||||
let step = self.step;
|
||||
let tx = context.tx.clone();
|
||||
|
||||
// GTK will spam the same value over and over
|
||||
let prev_value = Cell::new(scale.value());
|
||||
|
||||
scale.connect_scroll_event(move |scale, event| {
|
||||
let value = scale.value();
|
||||
let delta = event.delta().1.neg();
|
||||
|
||||
let delta = match (step, delta.is_sign_positive()) {
|
||||
(Some(step), true) => step,
|
||||
(Some(step), false) => -step,
|
||||
(None, _) => delta,
|
||||
};
|
||||
|
||||
scale.set_value(value + delta);
|
||||
Inhibit(false)
|
||||
});
|
||||
|
||||
scale.connect_change_value(move |scale, _, val| {
|
||||
// GTK will send values outside min/max range
|
||||
let val = val.clamp(min, max);
|
||||
|
@ -136,27 +136,34 @@ pub struct ItemButton {
|
||||
pub menu_state: Rc<RwLock<MenuState>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct AppearanceOptions {
|
||||
pub show_names: bool,
|
||||
pub show_icons: bool,
|
||||
pub icon_size: i32,
|
||||
}
|
||||
|
||||
impl ItemButton {
|
||||
pub fn new(
|
||||
item: &Item,
|
||||
show_names: bool,
|
||||
show_icons: bool,
|
||||
orientation: Orientation,
|
||||
appearance: AppearanceOptions,
|
||||
icon_theme: &IconTheme,
|
||||
orientation: Orientation,
|
||||
tx: &Sender<ModuleUpdateEvent<LauncherUpdate>>,
|
||||
controller_tx: &Sender<ItemEvent>,
|
||||
) -> Self {
|
||||
let mut button = Button::builder();
|
||||
|
||||
if show_names {
|
||||
if appearance.show_names {
|
||||
button = button.label(&item.name);
|
||||
}
|
||||
|
||||
let button = button.build();
|
||||
|
||||
if show_icons {
|
||||
if appearance.show_icons {
|
||||
let gtk_image = gtk::Image::new();
|
||||
let image = ImageProvider::parse(&item.app_id.clone(), icon_theme, 32);
|
||||
let image =
|
||||
ImageProvider::parse(&item.app_id.clone(), icon_theme, appearance.icon_size);
|
||||
match image {
|
||||
Ok(image) => {
|
||||
button.set_image(Some(>k_image));
|
||||
@ -217,7 +224,7 @@ impl ItemButton {
|
||||
|
||||
try_send!(
|
||||
tx,
|
||||
ModuleUpdateEvent::OpenPopup(Popup::widget_geometry(button, orientation,))
|
||||
ModuleUpdateEvent::OpenPopup(Popup::widget_geometry(button, orientation))
|
||||
);
|
||||
} else {
|
||||
try_send!(tx, ModuleUpdateEvent::ClosePopup);
|
||||
@ -232,7 +239,7 @@ impl ItemButton {
|
||||
Self {
|
||||
button,
|
||||
persistent: item.favorite,
|
||||
show_names,
|
||||
show_names: appearance.show_names,
|
||||
menu_state,
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ use self::open_state::OpenState;
|
||||
use crate::clients::wayland::{self, ToplevelChange};
|
||||
use crate::config::CommonConfig;
|
||||
use crate::desktop_file::find_desktop_file;
|
||||
use crate::modules::launcher::item::AppearanceOptions;
|
||||
use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext};
|
||||
use crate::{lock, read_lock, try_send, write_lock};
|
||||
use color_eyre::{Help, Report};
|
||||
@ -33,10 +34,17 @@ pub struct LauncherModule {
|
||||
#[serde(default = "crate::config::default_true")]
|
||||
show_icons: bool,
|
||||
|
||||
#[serde(default = "default_icon_size")]
|
||||
icon_size: i32,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub common: Option<CommonConfig>,
|
||||
}
|
||||
|
||||
const fn default_icon_size() -> i32 {
|
||||
32
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum LauncherUpdate {
|
||||
/// Adds item
|
||||
@ -318,8 +326,13 @@ impl Module<gtk::Box> for LauncherModule {
|
||||
|
||||
let controller_tx = context.controller_tx.clone();
|
||||
|
||||
let appearance_options = AppearanceOptions {
|
||||
show_names: self.show_names,
|
||||
show_icons: self.show_icons,
|
||||
icon_size: self.icon_size,
|
||||
};
|
||||
|
||||
let show_names = self.show_names;
|
||||
let show_icons = self.show_icons;
|
||||
let orientation = info.bar_position.get_orientation();
|
||||
|
||||
let mut buttons = IndexMap::<String, ItemButton>::new();
|
||||
@ -334,10 +347,9 @@ impl Module<gtk::Box> for LauncherModule {
|
||||
} else {
|
||||
let button = ItemButton::new(
|
||||
&item,
|
||||
show_names,
|
||||
show_icons,
|
||||
orientation,
|
||||
appearance_options,
|
||||
&icon_theme,
|
||||
orientation,
|
||||
&context.tx,
|
||||
&controller_tx,
|
||||
);
|
||||
|
@ -23,14 +23,14 @@ pub mod tray;
|
||||
pub mod workspaces;
|
||||
|
||||
use crate::bridge_channel::BridgeChannel;
|
||||
use crate::config::{BarPosition, CommonConfig};
|
||||
use crate::config::{BarPosition, CommonConfig, TransitionType};
|
||||
use crate::popup::{Popup, WidgetGeometry};
|
||||
use crate::{read_lock, send, write_lock};
|
||||
use color_eyre::Result;
|
||||
use glib::IsA;
|
||||
use gtk::gdk::{EventMask, Monitor};
|
||||
use gtk::prelude::*;
|
||||
use gtk::{Application, EventBox, IconTheme, Widget};
|
||||
use gtk::{Application, EventBox, IconTheme, Orientation, Revealer, Widget};
|
||||
use std::sync::{Arc, RwLock};
|
||||
use tokio::sync::mpsc;
|
||||
use tracing::debug;
|
||||
@ -234,12 +234,30 @@ fn setup_receiver<TSend>(
|
||||
|
||||
/// Takes a widget and adds it into a new `gtk::EventBox`.
|
||||
/// The event box container is returned.
|
||||
pub fn wrap_widget<W: IsA<Widget>>(widget: &W, common: CommonConfig) -> EventBox {
|
||||
pub fn wrap_widget<W: IsA<Widget>>(
|
||||
widget: &W,
|
||||
common: CommonConfig,
|
||||
orientation: Orientation,
|
||||
) -> EventBox {
|
||||
let revealer = Revealer::builder()
|
||||
.transition_type(
|
||||
common
|
||||
.transition_type
|
||||
.as_ref()
|
||||
.unwrap_or(&TransitionType::SlideStart)
|
||||
.to_revealer_transition_type(orientation),
|
||||
)
|
||||
.transition_duration(common.transition_duration.unwrap_or(250))
|
||||
.build();
|
||||
|
||||
revealer.add(widget);
|
||||
revealer.set_reveal_child(true);
|
||||
|
||||
let container = EventBox::new();
|
||||
container.add_events(EventMask::SCROLL_MASK);
|
||||
container.add(widget);
|
||||
container.add(&revealer);
|
||||
|
||||
common.install(&container);
|
||||
common.install(&container, &revealer);
|
||||
|
||||
container
|
||||
}
|
||||
|
@ -88,6 +88,12 @@ pub struct MusicModule {
|
||||
#[serde(default = "default_music_dir")]
|
||||
pub(crate) music_dir: PathBuf,
|
||||
|
||||
#[serde(default = "default_icon_size")]
|
||||
pub(crate) icon_size: i32,
|
||||
|
||||
#[serde(default = "default_cover_image_size")]
|
||||
pub(crate) cover_image_size: i32,
|
||||
|
||||
// -- Common --
|
||||
pub(crate) truncate: Option<TruncateMode>,
|
||||
|
||||
@ -138,3 +144,11 @@ fn default_icon_artist() -> String {
|
||||
fn default_music_dir() -> PathBuf {
|
||||
audio_dir().unwrap_or_else(|| home_dir().map(|dir| dir.join("Music")).unwrap_or_default())
|
||||
}
|
||||
|
||||
const fn default_icon_size() -> i32 {
|
||||
24
|
||||
}
|
||||
|
||||
const fn default_cover_image_size() -> i32 {
|
||||
128
|
||||
}
|
||||
|
@ -157,8 +157,8 @@ impl Module<Button> for MusicModule {
|
||||
let button_contents = gtk::Box::new(Orientation::Horizontal, 5);
|
||||
button.add(&button_contents);
|
||||
|
||||
let icon_play = new_icon_label(&self.icons.play, info.icon_theme, 24);
|
||||
let icon_pause = new_icon_label(&self.icons.pause, info.icon_theme, 24);
|
||||
let icon_play = new_icon_label(&self.icons.play, info.icon_theme, self.icon_size);
|
||||
let icon_pause = new_icon_label(&self.icons.pause, info.icon_theme, self.icon_size);
|
||||
let label = Label::new(None);
|
||||
|
||||
label.set_angle(info.bar_position.get_angle());
|
||||
@ -261,16 +261,16 @@ impl Module<Button> for MusicModule {
|
||||
|
||||
let controls_box = gtk::Box::builder().name("controls").build();
|
||||
|
||||
let btn_prev = new_icon_button(&icons.prev, icon_theme, 24);
|
||||
let btn_prev = new_icon_button(&icons.prev, icon_theme, self.icon_size);
|
||||
btn_prev.set_widget_name("btn-prev");
|
||||
|
||||
let btn_play = new_icon_button(&icons.play, icon_theme, 24);
|
||||
let btn_play = new_icon_button(&icons.play, icon_theme, self.icon_size);
|
||||
btn_play.set_widget_name("btn-play");
|
||||
|
||||
let btn_pause = new_icon_button(&icons.pause, icon_theme, 24);
|
||||
let btn_pause = new_icon_button(&icons.pause, icon_theme, self.icon_size);
|
||||
btn_pause.set_widget_name("btn-pause");
|
||||
|
||||
let btn_next = new_icon_button(&icons.next, icon_theme, 24);
|
||||
let btn_next = new_icon_button(&icons.next, icon_theme, self.icon_size);
|
||||
btn_next.set_widget_name("btn-next");
|
||||
|
||||
controls_box.add(&btn_prev);
|
||||
@ -290,7 +290,7 @@ impl Module<Button> for MusicModule {
|
||||
volume_slider.set_inverted(true);
|
||||
volume_slider.set_widget_name("slider");
|
||||
|
||||
let volume_icon = new_icon_label(&icons.volume, icon_theme, 24);
|
||||
let volume_icon = new_icon_label(&icons.volume, icon_theme, self.icon_size);
|
||||
volume_icon.style_context().add_class("icon");
|
||||
|
||||
volume_box.pack_start(&volume_slider, true, true, 0);
|
||||
@ -330,6 +330,7 @@ impl Module<Button> for MusicModule {
|
||||
|
||||
{
|
||||
let icon_theme = icon_theme.clone();
|
||||
let image_size = self.cover_image_size;
|
||||
|
||||
let mut prev_cover = None;
|
||||
rx.attach(None, move |update| {
|
||||
@ -338,9 +339,9 @@ impl Module<Button> for MusicModule {
|
||||
let new_cover = update.song.cover_path;
|
||||
if prev_cover != new_cover {
|
||||
prev_cover = new_cover.clone();
|
||||
let res = match new_cover
|
||||
.map(|cover_path| ImageProvider::parse(&cover_path, &icon_theme, 128))
|
||||
{
|
||||
let res = match new_cover.map(|cover_path| {
|
||||
ImageProvider::parse(&cover_path, &icon_theme, image_size)
|
||||
}) {
|
||||
Some(Ok(image)) => image.load_into_image(album_image.clone()),
|
||||
Some(Err(err)) => {
|
||||
album_image.set_from_pixbuf(None);
|
||||
@ -451,7 +452,7 @@ impl IconLabel {
|
||||
fn new(icon_input: &str, label: Option<&str>, icon_theme: &IconTheme) -> Self {
|
||||
let container = gtk::Box::new(Orientation::Horizontal, 5);
|
||||
|
||||
let icon = new_icon_label(icon_input, icon_theme, 32);
|
||||
let icon = new_icon_label(icon_input, icon_theme, 24);
|
||||
let label = Label::new(label);
|
||||
|
||||
icon.style_context().add_class("icon");
|
||||
|
@ -3,8 +3,12 @@ use crate::config::CommonConfig;
|
||||
use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext};
|
||||
use crate::{await_sync, try_send};
|
||||
use color_eyre::Result;
|
||||
use gtk::gdk_pixbuf::{Colorspace, InterpType};
|
||||
use gtk::prelude::*;
|
||||
use gtk::{IconLookupFlags, IconTheme, Image, Menu, MenuBar, MenuItem, SeparatorMenuItem};
|
||||
use gtk::{
|
||||
gdk_pixbuf, IconLookupFlags, IconTheme, Image, Label, Menu, MenuBar, MenuItem,
|
||||
SeparatorMenuItem,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use std::collections::HashMap;
|
||||
use stray::message::menu::{MenuItem as MenuItemInfo, MenuType};
|
||||
@ -20,9 +24,9 @@ pub struct TrayModule {
|
||||
pub common: Option<CommonConfig>,
|
||||
}
|
||||
|
||||
/// Gets a GTK `Image` component
|
||||
/// Attempts to get a GTK `Image` component
|
||||
/// for the status notifier item's icon.
|
||||
fn get_icon(item: &StatusNotifierItem) -> Option<Image> {
|
||||
fn get_image_from_icon_name(item: &StatusNotifierItem) -> Option<Image> {
|
||||
item.icon_theme_path.as_ref().and_then(|path| {
|
||||
let theme = IconTheme::new();
|
||||
theme.append_search_path(path);
|
||||
@ -34,6 +38,37 @@ fn get_icon(item: &StatusNotifierItem) -> Option<Image> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Attempts to get an image from the item pixmap.
|
||||
///
|
||||
/// The pixmap is supplied in ARGB32 format,
|
||||
/// which has 8 bits per sample and a bit stride of `4*width`.
|
||||
fn get_image_from_pixmap(item: &StatusNotifierItem) -> Option<Image> {
|
||||
const BITS_PER_SAMPLE: i32 = 8; //
|
||||
|
||||
let pixmap = item
|
||||
.icon_pixmap
|
||||
.as_ref()
|
||||
.and_then(|pixmap| pixmap.first())?;
|
||||
|
||||
let bytes = glib::Bytes::from(&pixmap.pixels);
|
||||
let row_stride = pixmap.width * 4; //
|
||||
|
||||
let pixbuf = gdk_pixbuf::Pixbuf::from_bytes(
|
||||
&bytes,
|
||||
Colorspace::Rgb,
|
||||
true,
|
||||
BITS_PER_SAMPLE,
|
||||
pixmap.width,
|
||||
pixmap.height,
|
||||
row_stride,
|
||||
);
|
||||
|
||||
let pixbuf = pixbuf
|
||||
.scale_simple(16, 16, InterpType::Bilinear)
|
||||
.unwrap_or(pixbuf);
|
||||
Some(Image::from_pixbuf(Some(&pixbuf)))
|
||||
}
|
||||
|
||||
/// Recursively gets GTK `MenuItem` components
|
||||
/// for the provided submenu array.
|
||||
fn get_menu_items(
|
||||
@ -147,13 +182,25 @@ impl Module<MenuBar> for TrayModule {
|
||||
address,
|
||||
menu,
|
||||
} => {
|
||||
let addr = &address;
|
||||
let menu_item = widgets.remove(address.as_str()).unwrap_or_else(|| {
|
||||
let menu_item = MenuItem::new();
|
||||
menu_item.style_context().add_class("item");
|
||||
if let Some(image) = get_icon(&item) {
|
||||
image.set_widget_name(address.as_str());
|
||||
menu_item.add(&image);
|
||||
}
|
||||
|
||||
get_image_from_icon_name(&item)
|
||||
.or_else(|| get_image_from_pixmap(&item))
|
||||
.map_or_else(
|
||||
|| {
|
||||
let label =
|
||||
Label::new(Some(item.title.as_ref().unwrap_or(addr)));
|
||||
menu_item.add(&label);
|
||||
},
|
||||
|image| {
|
||||
image.set_widget_name(address.as_str());
|
||||
menu_item.add(&image);
|
||||
},
|
||||
);
|
||||
|
||||
container.add(&menu_item);
|
||||
menu_item.show_all();
|
||||
menu_item
|
||||
|
@ -41,21 +41,29 @@ pub struct WorkspacesModule {
|
||||
#[serde(default)]
|
||||
sort: SortOrder,
|
||||
|
||||
#[serde(default = "default_icon_size")]
|
||||
icon_size: i32,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub common: Option<CommonConfig>,
|
||||
}
|
||||
|
||||
const fn default_icon_size() -> i32 {
|
||||
32
|
||||
}
|
||||
|
||||
/// Creates a button from a workspace
|
||||
fn create_button(
|
||||
name: &str,
|
||||
focused: bool,
|
||||
name_map: &HashMap<String, String>,
|
||||
icon_theme: &IconTheme,
|
||||
icon_size: i32,
|
||||
tx: &Sender<String>,
|
||||
) -> Button {
|
||||
let label = name_map.get(name).map_or(name, String::as_str);
|
||||
|
||||
let button = new_icon_button(label, icon_theme, 32);
|
||||
let button = new_icon_button(label, icon_theme, icon_size);
|
||||
button.set_widget_name(name);
|
||||
|
||||
let style_context = button.style_context();
|
||||
@ -157,6 +165,7 @@ impl Module<gtk::Box> for WorkspacesModule {
|
||||
let container = container.clone();
|
||||
let output_name = info.output_name.to_string();
|
||||
let icon_theme = info.icon_theme.clone();
|
||||
let icon_size = self.icon_size;
|
||||
|
||||
// keep track of whether init event has fired previously
|
||||
// since it fires for every workspace subscriber
|
||||
@ -174,6 +183,7 @@ impl Module<gtk::Box> for WorkspacesModule {
|
||||
workspace.focused,
|
||||
&name_map,
|
||||
&icon_theme,
|
||||
icon_size,
|
||||
&context.controller_tx,
|
||||
);
|
||||
container.add(&item);
|
||||
@ -209,6 +219,7 @@ impl Module<gtk::Box> for WorkspacesModule {
|
||||
workspace.focused,
|
||||
&name_map,
|
||||
&icon_theme,
|
||||
icon_size,
|
||||
&context.controller_tx,
|
||||
);
|
||||
|
||||
@ -233,6 +244,7 @@ impl Module<gtk::Box> for WorkspacesModule {
|
||||
workspace.focused,
|
||||
&name_map,
|
||||
&icon_theme,
|
||||
icon_size,
|
||||
&context.controller_tx,
|
||||
);
|
||||
|
||||
|
@ -229,7 +229,7 @@ impl Script {
|
||||
let mut args_list = vec!["-c", &self.cmd];
|
||||
|
||||
if let Some(args) = args {
|
||||
args_list.extend(args.iter().map(|s| s.as_str()));
|
||||
args_list.extend(args.iter().map(String::as_str));
|
||||
}
|
||||
|
||||
debug!("Running sh with args: {args_list:?}");
|
||||
@ -322,7 +322,7 @@ impl Script {
|
||||
///
|
||||
pub fn run_as_oneshot(&self, args: Option<&[String]>) {
|
||||
let script = self.clone();
|
||||
let args = args.map(|args| args.to_vec());
|
||||
let args = args.map(<[String]>::to_vec);
|
||||
|
||||
spawn(async move {
|
||||
match script.get_output(args.as_deref()).await {
|
||||
|
Loading…
Reference in New Issue
Block a user