Add config for loading keybindings
This commit is contained in:
parent
4796832521
commit
aab52b502c
7 changed files with 367 additions and 63 deletions
192
src/config.rs
Normal file
192
src/config.rs
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use serde::Deserialize;
|
||||
use smithay::wayland::seat::Keysym;
|
||||
pub use smithay::{
|
||||
backend::input::KeyState,
|
||||
wayland::seat::{keysyms as KeySyms, ModifiersState as KeyModifiers},
|
||||
};
|
||||
use std::{collections::HashMap, fs::OpenOptions, path::PathBuf};
|
||||
use xkbcommon::xkb;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Config {
|
||||
pub key_bindings: HashMap<KeyPattern, Action>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn load() -> Config {
|
||||
let mut locations = if let Ok(base) = xdg::BaseDirectories::new() {
|
||||
base.list_config_files_once("cosmic-comp.ron")
|
||||
} else {
|
||||
Vec::with_capacity(3)
|
||||
};
|
||||
if cfg!(debug_assertions) {
|
||||
if let Ok(mut cwd) = std::env::current_dir() {
|
||||
cwd.push("config.ron");
|
||||
locations.push(cwd);
|
||||
}
|
||||
}
|
||||
locations.push(PathBuf::from("/etc/cosmic-comp/config.ron"));
|
||||
locations.push(PathBuf::from("/etc/cosmic-comp.ron"));
|
||||
|
||||
for path in locations {
|
||||
if path.exists() {
|
||||
return ron::de::from_reader(OpenOptions::new().read(true).open(path).unwrap())
|
||||
.expect("Malformed config file");
|
||||
}
|
||||
}
|
||||
|
||||
Config {
|
||||
key_bindings: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
|
||||
pub enum KeyModifier {
|
||||
Ctrl,
|
||||
Alt,
|
||||
Shift,
|
||||
Logo,
|
||||
CapsLock,
|
||||
NumLock,
|
||||
}
|
||||
|
||||
impl std::ops::AddAssign<KeyModifier> for KeyModifiers {
|
||||
fn add_assign(&mut self, rhs: KeyModifier) {
|
||||
match rhs {
|
||||
KeyModifier::Ctrl => self.ctrl = true,
|
||||
KeyModifier::Alt => self.alt = true,
|
||||
KeyModifier::Shift => self.shift = true,
|
||||
KeyModifier::Logo => self.logo = true,
|
||||
KeyModifier::CapsLock => self.caps_lock = true,
|
||||
KeyModifier::NumLock => self.num_lock = true,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::BitOr for KeyModifier {
|
||||
type Output = KeyModifiers;
|
||||
|
||||
fn bitor(self, rhs: KeyModifier) -> Self::Output {
|
||||
let mut modifiers = self.into();
|
||||
modifiers += rhs;
|
||||
modifiers
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<KeyModifiers> for KeyModifier {
|
||||
fn into(self) -> KeyModifiers {
|
||||
let mut modifiers = KeyModifiers {
|
||||
ctrl: false,
|
||||
alt: false,
|
||||
shift: false,
|
||||
caps_lock: false,
|
||||
logo: false,
|
||||
num_lock: false,
|
||||
};
|
||||
modifiers += self;
|
||||
modifiers
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(transparent)]
|
||||
struct KeyModifiersDef(Vec<KeyModifier>);
|
||||
|
||||
impl From<KeyModifiersDef> for KeyModifiers {
|
||||
fn from(src: KeyModifiersDef) -> Self {
|
||||
src.0.into_iter().fold(
|
||||
KeyModifiers {
|
||||
ctrl: false,
|
||||
alt: false,
|
||||
shift: false,
|
||||
caps_lock: false,
|
||||
logo: false,
|
||||
num_lock: false,
|
||||
},
|
||||
|mut modis, modi| {
|
||||
modis += modi;
|
||||
modis
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn deserialize_KeyModifiers<'de, D>(deserializer: D) -> Result<KeyModifiers, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
KeyModifiersDef::deserialize(deserializer).map(Into::into)
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn deserialize_Keysym<'de, D>(deserializer: D) -> Result<Keysym, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
use serde::de::{Error, Unexpected};
|
||||
|
||||
let name = String::deserialize(deserializer)?;
|
||||
//let name = format!("KEY_{}", code);
|
||||
match xkb::keysym_from_name(&name, xkb::KEYSYM_NO_FLAGS) {
|
||||
KeySyms::KEY_NoSymbol => match xkb::keysym_from_name(&name, xkb::KEYSYM_CASE_INSENSITIVE) {
|
||||
KeySyms::KEY_NoSymbol => Err(<D::Error as Error>::invalid_value(
|
||||
Unexpected::Str(&name),
|
||||
&"One of the keysym names of xkbcommon.h without the 'KEY_' prefix",
|
||||
)),
|
||||
x => {
|
||||
slog_scope::warn!(
|
||||
"Key-Binding '{}' only matched case insensitive for {:?}",
|
||||
name,
|
||||
xkb::keysym_get_name(x)
|
||||
);
|
||||
Ok(x)
|
||||
}
|
||||
},
|
||||
x => Ok(x),
|
||||
}
|
||||
}
|
||||
|
||||
/// Describtion of a key combination that might be
|
||||
/// handled by the compositor.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Hash)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct KeyPattern {
|
||||
/// What modifiers are expected to be pressed alongside the key
|
||||
#[serde(deserialize_with = "deserialize_KeyModifiers")]
|
||||
pub modifiers: KeyModifiers,
|
||||
/// The actual key, that was pressed
|
||||
#[serde(deserialize_with = "deserialize_Keysym")]
|
||||
pub key: u32,
|
||||
}
|
||||
|
||||
impl KeyPattern {
|
||||
pub fn new(modifiers: impl Into<KeyModifiers>, key: u32) -> KeyPattern {
|
||||
KeyPattern {
|
||||
modifiers: modifiers.into(),
|
||||
key,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone, PartialEq, Eq)]
|
||||
pub enum Action {
|
||||
Terminate,
|
||||
Debug,
|
||||
Close,
|
||||
Workspace(u8),
|
||||
MoveToWorkspace(u8),
|
||||
Focus(FocusAction),
|
||||
Spawn(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum FocusAction {
|
||||
Left,
|
||||
Right,
|
||||
Up,
|
||||
Down,
|
||||
}
|
||||
130
src/input/mod.rs
130
src/input/mod.rs
|
|
@ -1,9 +1,8 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::state::Common;
|
||||
use smithay::{backend::input::KeyState, wayland::seat::keysyms};
|
||||
use crate::{config::Action, state::Common};
|
||||
use smithay::{
|
||||
backend::input::{Device, DeviceCapability, InputBackend, InputEvent},
|
||||
backend::input::{Device, DeviceCapability, InputBackend, InputEvent, KeyState},
|
||||
desktop::{layer_map_for_output, Space, WindowSurfaceType},
|
||||
reexports::wayland_server::{protocol::wl_surface::WlSurface, Display},
|
||||
utils::{Logical, Point},
|
||||
|
|
@ -213,66 +212,8 @@ impl Common {
|
|||
return FilterResult::Intercept(());
|
||||
}
|
||||
|
||||
// here we can handle global shortcuts and the like
|
||||
if modifiers.logo
|
||||
&& handle.raw_syms().contains(&keysyms::KEY_Return)
|
||||
&& state == KeyState::Pressed
|
||||
{
|
||||
if let Err(err) = std::process::Command::new("gnome-terminal")
|
||||
.env("WAYLAND_DISPLAY", &self.socket)
|
||||
.spawn()
|
||||
{
|
||||
slog_scope::warn!("Failed to spawn terminal: {}", err);
|
||||
}
|
||||
userdata.get::<SupressedKeys>().unwrap().add(&handle);
|
||||
return FilterResult::Intercept(());
|
||||
}
|
||||
|
||||
if modifiers.logo
|
||||
&& handle
|
||||
.raw_syms()
|
||||
.iter()
|
||||
.any(|sym| *sym >= keysyms::KEY_0 && *sym <= keysyms::KEY_9)
|
||||
&& state == KeyState::Pressed
|
||||
{
|
||||
let current_output = active_output(seat, &self);
|
||||
let key_num = handle
|
||||
.raw_syms()
|
||||
.iter()
|
||||
.find(|sym| {
|
||||
**sym >= keysyms::KEY_0 && **sym <= keysyms::KEY_9
|
||||
})
|
||||
.unwrap()
|
||||
- keysyms::KEY_0;
|
||||
let workspace = match key_num {
|
||||
0 => 9,
|
||||
x => x - 1,
|
||||
};
|
||||
self.shell.activate(¤t_output, workspace as usize);
|
||||
userdata.get::<SupressedKeys>().unwrap().add(&handle);
|
||||
return FilterResult::Intercept(());
|
||||
}
|
||||
|
||||
if modifiers.logo
|
||||
&& modifiers.shift
|
||||
&& handle.raw_syms().contains(&keysyms::KEY_Escape)
|
||||
&& state == KeyState::Pressed
|
||||
{
|
||||
self.should_stop = true;
|
||||
}
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
{
|
||||
self.egui.modifiers = modifiers.clone();
|
||||
if self.seats.iter().position(|x| x == seat).unwrap() == 0
|
||||
&& modifiers.logo
|
||||
&& handle.raw_syms().contains(&keysyms::KEY_Escape)
|
||||
&& state == KeyState::Pressed
|
||||
{
|
||||
self.egui.active = !self.egui.active;
|
||||
userdata.get::<SupressedKeys>().unwrap().add(&handle);
|
||||
return FilterResult::Intercept(());
|
||||
}
|
||||
if self.seats.iter().position(|x| x == seat).unwrap() == 0
|
||||
&& self.egui.active
|
||||
{
|
||||
|
|
@ -295,6 +236,73 @@ impl Common {
|
|||
}
|
||||
}
|
||||
|
||||
// here we can handle global shortcuts and the like
|
||||
for (binding, action) in self.config.key_bindings.iter() {
|
||||
if state == KeyState::Pressed
|
||||
&& binding.modifiers == *modifiers
|
||||
&& handle.raw_syms().contains(&binding.key)
|
||||
{
|
||||
match action {
|
||||
Action::Terminate => {
|
||||
userdata
|
||||
.get::<SupressedKeys>()
|
||||
.unwrap()
|
||||
.add(&handle);
|
||||
self.should_stop = true;
|
||||
return FilterResult::Intercept(());
|
||||
}
|
||||
#[cfg(feature = "debug")]
|
||||
Action::Debug => {
|
||||
self.egui.active = !self.egui.active;
|
||||
userdata
|
||||
.get::<SupressedKeys>()
|
||||
.unwrap()
|
||||
.add(&handle);
|
||||
return FilterResult::Intercept(());
|
||||
}
|
||||
#[cfg(not(feature = "debug"))]
|
||||
Action::Debug => slog_scope::info!(
|
||||
"Debug overlay not included in this version"
|
||||
),
|
||||
Action::Close => { /* TODO */ }
|
||||
Action::Workspace(key_num) => {
|
||||
let current_output = active_output(seat, &self);
|
||||
let workspace = match key_num {
|
||||
0 => 9,
|
||||
x => x - 1,
|
||||
};
|
||||
self.shell
|
||||
.activate(¤t_output, workspace as usize);
|
||||
userdata
|
||||
.get::<SupressedKeys>()
|
||||
.unwrap()
|
||||
.add(&handle);
|
||||
return FilterResult::Intercept(());
|
||||
}
|
||||
Action::MoveToWorkspace(num) => { /* TODO */ }
|
||||
Action::Focus(focus) => match focus {
|
||||
_ => { /* TODO */ }
|
||||
},
|
||||
Action::Spawn(command) => {
|
||||
if let Err(err) =
|
||||
std::process::Command::new("/bin/sh")
|
||||
.arg("-c")
|
||||
.arg(command)
|
||||
.env("WAYLAND_DISPLAY", &self.socket)
|
||||
.spawn()
|
||||
{
|
||||
slog_scope::warn!("Failed to spawn: {}", err);
|
||||
}
|
||||
userdata
|
||||
.get::<SupressedKeys>()
|
||||
.unwrap()
|
||||
.add(&handle);
|
||||
return FilterResult::Intercept(());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FilterResult::Forward
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ use anyhow::{Context, Result};
|
|||
use std::{ffi::OsString, sync::atomic::Ordering};
|
||||
|
||||
pub mod backend;
|
||||
pub mod config;
|
||||
pub mod input;
|
||||
mod logger;
|
||||
pub mod shell;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
use crate::{
|
||||
backend::{kms::KmsState, winit::WinitState, x11::X11State},
|
||||
config::Config,
|
||||
logger::LogState,
|
||||
shell::{init_shell, Shell},
|
||||
};
|
||||
|
|
@ -35,6 +36,8 @@ pub struct State {
|
|||
}
|
||||
|
||||
pub struct Common {
|
||||
pub config: Config,
|
||||
|
||||
pub display: Rc<RefCell<Display>>,
|
||||
pub socket: OsString,
|
||||
pub event_loop_handle: LoopHandle<'static, State>,
|
||||
|
|
@ -167,6 +170,8 @@ impl State {
|
|||
|
||||
State {
|
||||
common: Common {
|
||||
config: Config::load(),
|
||||
|
||||
display: Rc::new(RefCell::new(display)),
|
||||
socket,
|
||||
event_loop_handle: handle,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue