Add config for loading keybindings

This commit is contained in:
Victoria Brekenfeld 2022-03-28 23:45:30 +02:00
parent 4796832521
commit aab52b502c
7 changed files with 367 additions and 63 deletions

192
src/config.rs Normal file
View 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,
}

View file

@ -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(&current_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(&current_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
},
);

View file

@ -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;

View file

@ -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,