mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-07-12 08:02:40 +02:00
feat(bar): send commands by mouse/touchpad/screen
This commit makes it possible to send commands from the bar by using the mouse/touchpad/touchscreen. Komorebi or custom commands can be sent by clicking on the mouse's primary, secondary, middle, back or forward buttons. As the primary single click is already used by widgets, only primary double clicks can send commands. This limitation is due to Egui also triggering 2 single clicks before a double click is triggered. Egui does not have an implementation for stopping event propagation out of the box and would be too much work to include. Similarly, commands can be sent on every "tick" of mouse scrolling, touchpad or touchscreen swiping in any of the 4 directions. This "tick" can be adjusted to fit user's preference. This is due to the fact, that Egui does not have an event for when a mouse "tick" occurs. It instead gives a number of points that the user scrolled/swiped on each frame. PR: #1403
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
use crate::bar::exec_powershell;
|
||||
use crate::render::Grouping;
|
||||
use crate::widgets::widget::WidgetConfig;
|
||||
use crate::DEFAULT_PADDING;
|
||||
@@ -5,7 +6,9 @@ use eframe::egui::Pos2;
|
||||
use eframe::egui::TextBuffer;
|
||||
use eframe::egui::Vec2;
|
||||
use komorebi_client::KomorebiTheme;
|
||||
use komorebi_client::PathExt;
|
||||
use komorebi_client::Rect;
|
||||
use komorebi_client::SocketMessage;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::collections::HashMap;
|
||||
@@ -90,6 +93,8 @@ pub struct KomobarConfig {
|
||||
pub widget_spacing: Option<f32>,
|
||||
/// Visual grouping for widgets
|
||||
pub grouping: Option<Grouping>,
|
||||
/// Options for mouse interaction on the bar
|
||||
pub mouse: Option<MouseConfig>,
|
||||
/// Left side widgets (ordered left-to-right)
|
||||
pub left_widgets: Vec<WidgetConfig>,
|
||||
/// Center widgets (ordered left-to-right)
|
||||
@@ -325,6 +330,147 @@ pub fn get_individual_spacing(
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub enum MouseMessage {
|
||||
/// Send a message to the komorebi client.
|
||||
/// By default, a batch of messages are sent in the following order:
|
||||
/// FocusMonitorAtCursor =>
|
||||
/// MouseFollowsFocus(false) =>
|
||||
/// {message} =>
|
||||
/// MouseFollowsFocus({original.value})
|
||||
///
|
||||
/// Example:
|
||||
/// ```json
|
||||
/// "on_extra2_click": {
|
||||
/// "message": {
|
||||
/// "type": "NewWorkspace"
|
||||
/// }
|
||||
/// },
|
||||
/// ```
|
||||
/// or:
|
||||
/// ```json
|
||||
/// "on_middle_click": {
|
||||
/// "focus_monitor_at_cursor": false,
|
||||
/// "ignore_mouse_follows_focus": false,
|
||||
/// "message": {
|
||||
/// "type": "TogglePause"
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
/// or:
|
||||
/// ```json
|
||||
/// "on_scroll_up": {
|
||||
/// "message": {
|
||||
/// "type": "CycleFocusWorkspace",
|
||||
/// "content": "Previous"
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[serde(untagged)]
|
||||
Komorebi(KomorebiMouseMessage),
|
||||
/// Execute a custom command.
|
||||
/// CMD (%variable%), Bash ($variable) and PowerShell ($Env:variable) variables will be resolved.
|
||||
/// Example: `komorebic toggle-pause`
|
||||
#[serde(untagged)]
|
||||
Command(String),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct KomorebiMouseMessage {
|
||||
/// Send the FocusMonitorAtCursor message (default:true)
|
||||
pub focus_monitor_at_cursor: Option<bool>,
|
||||
/// Wrap the {message} with a MouseFollowsFocus(false) and MouseFollowsFocus({original.value}) message (default:true)
|
||||
pub ignore_mouse_follows_focus: Option<bool>,
|
||||
/// The message to send to the komorebi client
|
||||
pub message: komorebi_client::SocketMessage,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct MouseConfig {
|
||||
/// Command to send on primary/left double button click
|
||||
pub on_primary_double_click: Option<MouseMessage>,
|
||||
/// Command to send on secondary/right button click
|
||||
pub on_secondary_click: Option<MouseMessage>,
|
||||
/// Command to send on middle button click
|
||||
pub on_middle_click: Option<MouseMessage>,
|
||||
/// Command to send on extra1/back button click
|
||||
pub on_extra1_click: Option<MouseMessage>,
|
||||
/// Command to send on extra2/forward button click
|
||||
pub on_extra2_click: Option<MouseMessage>,
|
||||
|
||||
/// Defines how many points a user needs to scroll vertically to make a "tick" on a mouse/touchpad/touchscreen (default: 30)
|
||||
pub vertical_scroll_threshold: Option<f32>,
|
||||
/// Command to send on scrolling up (every tick)
|
||||
pub on_scroll_up: Option<MouseMessage>,
|
||||
/// Command to send on scrolling down (every tick)
|
||||
pub on_scroll_down: Option<MouseMessage>,
|
||||
|
||||
/// Defines how many points a user needs to scroll horizontally to make a "tick" on a mouse/touchpad/touchscreen (default: 30)
|
||||
pub horizontal_scroll_threshold: Option<f32>,
|
||||
/// Command to send on scrolling left (every tick)
|
||||
pub on_scroll_left: Option<MouseMessage>,
|
||||
/// Command to send on scrolling right (every tick)
|
||||
pub on_scroll_right: Option<MouseMessage>,
|
||||
}
|
||||
|
||||
impl MouseConfig {
|
||||
pub fn has_command(&self) -> bool {
|
||||
[
|
||||
&self.on_primary_double_click,
|
||||
&self.on_secondary_click,
|
||||
&self.on_middle_click,
|
||||
&self.on_extra1_click,
|
||||
&self.on_extra2_click,
|
||||
&self.on_scroll_up,
|
||||
&self.on_scroll_down,
|
||||
&self.on_scroll_left,
|
||||
&self.on_scroll_right,
|
||||
]
|
||||
.iter()
|
||||
.any(|opt| matches!(opt, Some(MouseMessage::Command(_))))
|
||||
}
|
||||
}
|
||||
|
||||
impl MouseMessage {
|
||||
pub fn execute(&self, mouse_follows_focus: bool) {
|
||||
match self {
|
||||
MouseMessage::Komorebi(config) => {
|
||||
let mut messages = Vec::new();
|
||||
|
||||
if config.focus_monitor_at_cursor.unwrap_or(true) {
|
||||
messages.push(SocketMessage::FocusMonitorAtCursor);
|
||||
}
|
||||
|
||||
if config.ignore_mouse_follows_focus.unwrap_or(true) {
|
||||
messages.push(SocketMessage::MouseFollowsFocus(false));
|
||||
messages.push(config.message.clone());
|
||||
messages.push(SocketMessage::MouseFollowsFocus(mouse_follows_focus));
|
||||
} else {
|
||||
messages.push(config.message.clone());
|
||||
}
|
||||
|
||||
tracing::debug!("Sending messages: {messages:?}");
|
||||
|
||||
if komorebi_client::send_batch(messages.into_iter()).is_err() {
|
||||
tracing::error!("could not send commands");
|
||||
}
|
||||
}
|
||||
MouseMessage::Command(cmd) => {
|
||||
tracing::debug!("Executing command: {}", cmd);
|
||||
|
||||
let cmd_no_env = cmd.replace_env();
|
||||
|
||||
if exec_powershell(cmd_no_env.to_str().expect("Invalid command")).is_err() {
|
||||
tracing::error!("Failed to execute '{}'", cmd);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl KomobarConfig {
|
||||
pub fn read(path: &PathBuf) -> color_eyre::Result<Self> {
|
||||
let content = std::fs::read_to_string(path)?;
|
||||
|
||||
Reference in New Issue
Block a user