mirror of
https://github.com/Zedfrigg/ironbar.git
synced 2025-08-17 23:01:04 +02:00
feat: libinput keys
module
Adds a new module which shows the status of toggle mod keys (capslock, num lock, scroll lock). Resolves #700
This commit is contained in:
parent
353ee92d48
commit
ccfe73f6a7
20 changed files with 799 additions and 107 deletions
231
src/modules/keys.rs
Normal file
231
src/modules/keys.rs
Normal file
|
@ -0,0 +1,231 @@
|
|||
use color_eyre::Result;
|
||||
use gtk::prelude::*;
|
||||
use serde::Deserialize;
|
||||
use std::ops::Deref;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
use super::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext};
|
||||
use crate::clients::libinput::{Event, Key, KeyEvent};
|
||||
use crate::config::CommonConfig;
|
||||
use crate::gtk_helpers::IronbarGtkExt;
|
||||
use crate::image::IconLabel;
|
||||
use crate::{glib_recv, module_impl, module_update, send_async, spawn};
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
|
||||
pub struct KeysModule {
|
||||
/// Whether to show capslock indicator.
|
||||
///
|
||||
/// **Default**: `true`
|
||||
#[serde(default = "crate::config::default_true")]
|
||||
show_caps: bool,
|
||||
|
||||
/// Whether to show num lock indicator.
|
||||
///
|
||||
/// **Default**: `true`
|
||||
#[serde(default = "crate::config::default_true")]
|
||||
show_num: bool,
|
||||
|
||||
/// Whether to show scroll lock indicator.
|
||||
///
|
||||
/// **Default**: `true`
|
||||
#[serde(default = "crate::config::default_true")]
|
||||
show_scroll: bool,
|
||||
|
||||
/// Size to render the icons at, in pixels (image icons only).
|
||||
///
|
||||
/// **Default** `32`
|
||||
#[serde(default = "default_icon_size")]
|
||||
icon_size: i32,
|
||||
|
||||
/// Player state icons.
|
||||
///
|
||||
/// See [icons](#icons).
|
||||
#[serde(default)]
|
||||
icons: Icons,
|
||||
|
||||
/// The Wayland seat to attach to.
|
||||
/// You almost certainly do not need to change this.
|
||||
///
|
||||
/// **Default**: `seat0`
|
||||
#[serde(default = "default_seat")]
|
||||
seat: String,
|
||||
|
||||
/// See [common options](module-level-options#common-options).
|
||||
#[serde(flatten)]
|
||||
pub common: Option<CommonConfig>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
|
||||
struct Icons {
|
||||
/// Icon to show when capslock is enabled.
|
||||
///
|
||||
/// **Default**: ``
|
||||
#[serde(default = "default_icon_caps")]
|
||||
caps_on: String,
|
||||
|
||||
/// Icon to show when capslock is disabled.
|
||||
///
|
||||
/// **Default**: `""`
|
||||
#[serde(default)]
|
||||
caps_off: String,
|
||||
|
||||
/// Icon to show when num lock is enabled.
|
||||
///
|
||||
/// **Default**: ``
|
||||
#[serde(default = "default_icon_num")]
|
||||
num_on: String,
|
||||
|
||||
/// Icon to show when num lock is disabled.
|
||||
///
|
||||
/// **Default**: `""`
|
||||
#[serde(default)]
|
||||
num_off: String,
|
||||
|
||||
/// Icon to show when scroll lock is enabled.
|
||||
///
|
||||
/// **Default**: ``
|
||||
#[serde(default = "default_icon_scroll")]
|
||||
scroll_on: String,
|
||||
|
||||
/// Icon to show when scroll lock is disabled.
|
||||
///
|
||||
/// **Default**: `""`
|
||||
#[serde(default)]
|
||||
scroll_off: String,
|
||||
}
|
||||
|
||||
impl Default for Icons {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
caps_on: default_icon_caps(),
|
||||
caps_off: String::new(),
|
||||
num_on: default_icon_num(),
|
||||
num_off: String::new(),
|
||||
scroll_on: default_icon_scroll(),
|
||||
scroll_off: String::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const fn default_icon_size() -> i32 {
|
||||
32
|
||||
}
|
||||
|
||||
fn default_seat() -> String {
|
||||
String::from("seat0")
|
||||
}
|
||||
|
||||
fn default_icon_caps() -> String {
|
||||
String::from("")
|
||||
}
|
||||
|
||||
fn default_icon_num() -> String {
|
||||
String::from("")
|
||||
}
|
||||
|
||||
fn default_icon_scroll() -> String {
|
||||
String::from("")
|
||||
}
|
||||
|
||||
impl Module<gtk::Box> for KeysModule {
|
||||
type SendMessage = KeyEvent;
|
||||
type ReceiveMessage = ();
|
||||
|
||||
module_impl!("keys");
|
||||
|
||||
fn spawn_controller(
|
||||
&self,
|
||||
_info: &ModuleInfo,
|
||||
context: &WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
|
||||
_rx: mpsc::Receiver<Self::ReceiveMessage>,
|
||||
) -> Result<()> {
|
||||
let client = context.ironbar.clients.borrow_mut().libinput(&self.seat);
|
||||
|
||||
let tx = context.tx.clone();
|
||||
spawn(async move {
|
||||
let mut rx = client.subscribe();
|
||||
while let Ok(ev) = rx.recv().await {
|
||||
match ev {
|
||||
Event::Device => {
|
||||
for key in [Key::Caps, Key::Num, Key::Scroll] {
|
||||
module_update!(
|
||||
tx,
|
||||
KeyEvent {
|
||||
key: Key::Caps,
|
||||
state: client.get_state(key)
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
Event::Key(ev) => {
|
||||
send_async!(tx, ModuleUpdateEvent::Update(ev));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn into_widget(
|
||||
self,
|
||||
context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
|
||||
info: &ModuleInfo,
|
||||
) -> Result<ModuleParts<gtk::Box>> {
|
||||
let container = gtk::Box::new(info.bar_position.orientation(), 5);
|
||||
|
||||
let caps = IconLabel::new(&self.icons.caps_off, info.icon_theme, self.icon_size);
|
||||
let num = IconLabel::new(&self.icons.num_off, info.icon_theme, self.icon_size);
|
||||
let scroll = IconLabel::new(&self.icons.scroll_off, info.icon_theme, self.icon_size);
|
||||
|
||||
if self.show_caps {
|
||||
caps.add_class("key");
|
||||
caps.add_class("caps");
|
||||
container.add(caps.deref());
|
||||
}
|
||||
|
||||
if self.show_num {
|
||||
num.add_class("key");
|
||||
num.add_class("num");
|
||||
container.add(num.deref());
|
||||
}
|
||||
|
||||
if self.show_scroll {
|
||||
scroll.add_class("key");
|
||||
scroll.add_class("scroll");
|
||||
container.add(scroll.deref());
|
||||
}
|
||||
|
||||
let icons = self.icons;
|
||||
let handle_event = move |ev: KeyEvent| {
|
||||
let parts = match (ev.key, ev.state) {
|
||||
(Key::Caps, true) if self.show_caps => Some((&caps, icons.caps_on.as_str())),
|
||||
(Key::Caps, false) if self.show_caps => Some((&caps, icons.caps_off.as_str())),
|
||||
(Key::Num, true) if self.show_num => Some((&num, icons.num_on.as_str())),
|
||||
(Key::Num, false) if self.show_num => Some((&num, icons.num_off.as_str())),
|
||||
(Key::Scroll, true) if self.show_scroll => {
|
||||
Some((&scroll, icons.scroll_on.as_str()))
|
||||
}
|
||||
(Key::Scroll, false) if self.show_scroll => {
|
||||
Some((&scroll, icons.scroll_off.as_str()))
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
|
||||
if let Some((label, input)) = parts {
|
||||
label.set_label(Some(input));
|
||||
|
||||
if ev.state {
|
||||
label.add_class("enabled");
|
||||
} else {
|
||||
label.remove_class("enabled");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
glib_recv!(context.subscribe(), handle_event);
|
||||
Ok(ModuleParts::new(container, None))
|
||||
}
|
||||
}
|
|
@ -31,6 +31,8 @@ pub mod clock;
|
|||
pub mod custom;
|
||||
#[cfg(feature = "focused")]
|
||||
pub mod focused;
|
||||
#[cfg(feature = "keys")]
|
||||
pub mod keys;
|
||||
pub mod label;
|
||||
#[cfg(feature = "launcher")]
|
||||
pub mod launcher;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use std::cell::RefMut;
|
||||
use std::ops::Deref;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
@ -17,7 +18,7 @@ use crate::clients::music::{
|
|||
};
|
||||
use crate::clients::Clients;
|
||||
use crate::gtk_helpers::{IronbarGtkExt, IronbarLabelExt};
|
||||
use crate::image::{new_icon_button, new_icon_label, ImageProvider};
|
||||
use crate::image::{new_icon_button, IconLabel, ImageProvider};
|
||||
use crate::modules::PopupButton;
|
||||
use crate::modules::{
|
||||
Module, ModuleInfo, ModuleParts, ModulePopup, ModuleUpdateEvent, WidgetContext,
|
||||
|
@ -187,8 +188,8 @@ impl Module<Button> for MusicModule {
|
|||
|
||||
button.add(&button_contents);
|
||||
|
||||
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 icon_play = IconLabel::new(&self.icons.play, info.icon_theme, self.icon_size);
|
||||
let icon_pause = IconLabel::new(&self.icons.pause, info.icon_theme, self.icon_size);
|
||||
|
||||
let label = Label::builder()
|
||||
.use_markup(true)
|
||||
|
@ -199,8 +200,8 @@ impl Module<Button> for MusicModule {
|
|||
label.truncate(truncate);
|
||||
}
|
||||
|
||||
button_contents.add(&icon_pause);
|
||||
button_contents.add(&icon_play);
|
||||
button_contents.add(icon_pause.deref());
|
||||
button_contents.add(icon_play.deref());
|
||||
button_contents.add(&label);
|
||||
|
||||
{
|
||||
|
@ -282,9 +283,9 @@ impl Module<Button> for MusicModule {
|
|||
let icons = self.icons;
|
||||
|
||||
let info_box = gtk::Box::new(Orientation::Vertical, 10);
|
||||
let title_label = IconLabel::new(&icons.track, None, icon_theme);
|
||||
let album_label = IconLabel::new(&icons.album, None, icon_theme);
|
||||
let artist_label = IconLabel::new(&icons.artist, None, icon_theme);
|
||||
let title_label = IconPrefixedLabel::new(&icons.track, None, icon_theme);
|
||||
let album_label = IconPrefixedLabel::new(&icons.album, None, icon_theme);
|
||||
let artist_label = IconPrefixedLabel::new(&icons.artist, None, icon_theme);
|
||||
|
||||
title_label.container.add_class("title");
|
||||
album_label.container.add_class("album");
|
||||
|
@ -323,11 +324,11 @@ impl Module<Button> for MusicModule {
|
|||
volume_slider.set_inverted(true);
|
||||
volume_slider.add_class("slider");
|
||||
|
||||
let volume_icon = new_icon_label(&icons.volume, icon_theme, self.icon_size);
|
||||
let volume_icon = IconLabel::new(&icons.volume, icon_theme, self.icon_size);
|
||||
volume_icon.add_class("icon");
|
||||
|
||||
volume_box.pack_start(&volume_slider, true, true, 0);
|
||||
volume_box.pack_end(&volume_icon, false, false, 0);
|
||||
volume_box.pack_end(volume_icon.deref(), false, false, 0);
|
||||
|
||||
main_container.add(&album_image);
|
||||
main_container.add(&info_box);
|
||||
|
@ -496,7 +497,7 @@ impl Module<Button> for MusicModule {
|
|||
}
|
||||
}
|
||||
|
||||
fn update_popup_metadata_label(text: Option<String>, label: &IconLabel) {
|
||||
fn update_popup_metadata_label(text: Option<String>, label: &IconPrefixedLabel) {
|
||||
match text {
|
||||
Some(value) => {
|
||||
label.label.set_label_escaped(&value);
|
||||
|
@ -536,16 +537,16 @@ fn get_token_value(song: &Track, token: &str) -> String {
|
|||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct IconLabel {
|
||||
struct IconPrefixedLabel {
|
||||
label: Label,
|
||||
container: gtk::Box,
|
||||
}
|
||||
|
||||
impl IconLabel {
|
||||
impl IconPrefixedLabel {
|
||||
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, 24);
|
||||
let icon = IconLabel::new(icon_input, icon_theme, 24);
|
||||
|
||||
let mut builder = Label::builder().use_markup(true);
|
||||
|
||||
|
@ -558,7 +559,7 @@ impl IconLabel {
|
|||
icon.add_class("icon-box");
|
||||
label.add_class("label");
|
||||
|
||||
container.add(&icon);
|
||||
container.add(icon.deref());
|
||||
container.add(&label);
|
||||
|
||||
Self { label, container }
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue