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

63
Cargo.lock generated
View file

@ -112,6 +112,12 @@ dependencies = [
"rustc-demangle",
]
[[package]]
name = "base64"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
[[package]]
name = "bitflags"
version = "1.3.2"
@ -291,6 +297,7 @@ dependencies = [
"egui",
"id_tree",
"indexmap",
"ron",
"serde",
"serde_json",
"slog",
@ -303,6 +310,8 @@ dependencies = [
"thiserror",
"wayland-scanner",
"xcursor",
"xdg",
"xkbcommon",
]
[[package]]
@ -366,6 +375,15 @@ dependencies = [
"syn",
]
[[package]]
name = "dirs"
version = "4.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059"
dependencies = [
"dirs-sys",
]
[[package]]
name = "dirs-next"
version = "2.0.0"
@ -376,6 +394,17 @@ dependencies = [
"dirs-sys-next",
]
[[package]]
name = "dirs-sys"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6"
dependencies = [
"libc",
"redox_users",
"winapi",
]
[[package]]
name = "dirs-sys-next"
version = "0.1.2"
@ -1134,6 +1163,17 @@ dependencies = [
"winapi",
]
[[package]]
name = "ron"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b861ecaade43ac97886a512b360d01d66be9f41f3c61088b42cedf92e03d678"
dependencies = [
"base64",
"bitflags",
"serde",
]
[[package]]
name = "rustc-demangle"
version = "0.1.21"
@ -1175,6 +1215,20 @@ name = "serde"
version = "1.0.136"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.136"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
@ -1734,6 +1788,15 @@ dependencies = [
"nom",
]
[[package]]
name = "xdg"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c4583db5cbd4c4c0303df2d15af80f0539db703fa1c68802d4cbbd2dd0f88f6"
dependencies = [
"dirs",
]
[[package]]
name = "xkbcommon"
version = "0.4.0"

View file

@ -13,14 +13,17 @@ slog-term = "2.8"
slog-async = "2.7"
slog-scope = "4.4"
slog-stdlog = "4.1"
serde = { version = "1", optional = true }
serde = { version = "1", features = ["derive"] }
serde_json = { version = "1", optional = true }
egui = { version = "0.16", optional = true }
edid-rs = { version = "0.1" }
thiserror = "1.0.26"
xcursor = "0.3.3"
id_tree = "1.8.0"
xkbcommon = "0.4"
indexmap = "1.8.0"
xdg = "^2.1"
ron = "0.7"
[dependencies.smithay]
version = "0.3"
@ -39,7 +42,7 @@ wayland-scanner = "0.29"
[features]
default = []
debug = ["egui", "smithay-egui", "serde", "serde_json"]
debug = ["egui", "smithay-egui", "serde_json"]
[profile.dev]
lto = "thin"

32
config.ron Normal file
View file

@ -0,0 +1,32 @@
(
key_bindings: {
(modifiers: [Logo, Shift], key: "Escape"): Terminate,
(modifiers: [Logo], key: "Escape"): Debug,
(modifiers: [Logo, Shift], key: "Q"): Close,
(modifiers: [Logo], key: "1"): Workspace(1),
(modifiers: [Logo], key: "2"): Workspace(2),
(modifiers: [Logo], key: "3"): Workspace(3),
(modifiers: [Logo], key: "4"): Workspace(4),
(modifiers: [Logo], key: "5"): Workspace(5),
(modifiers: [Logo], key: "6"): Workspace(6),
(modifiers: [Logo], key: "7"): Workspace(7),
(modifiers: [Logo], key: "8"): Workspace(8),
(modifiers: [Logo], key: "9"): Workspace(9),
(modifiers: [Logo], key: "0"): Workspace(0),
(modifiers: [Logo, Shift], key: "1"): MoveToWorkspace(1),
(modifiers: [Logo, Shift], key: "2"): MoveToWorkspace(2),
(modifiers: [Logo, Shift], key: "3"): MoveToWorkspace(3),
(modifiers: [Logo, Shift], key: "4"): MoveToWorkspace(4),
(modifiers: [Logo, Shift], key: "5"): MoveToWorkspace(5),
(modifiers: [Logo, Shift], key: "6"): MoveToWorkspace(6),
(modifiers: [Logo, Shift], key: "7"): MoveToWorkspace(7),
(modifiers: [Logo, Shift], key: "8"): MoveToWorkspace(8),
(modifiers: [Logo, Shift], key: "9"): MoveToWorkspace(9),
(modifiers: [Logo, Shift], key: "0"): MoveToWorkspace(0),
(modifiers: [Logo], key: "Left"): Focus(Left),
(modifiers: [Logo], key: "Right"): Focus(Right),
(modifiers: [Logo], key: "Up"): Focus(Up),
(modifiers: [Logo], key: "Down"): Focus(Down),
(modifiers: [Ctrl], key: "Return"): Spawn("gnome-terminal"),
}
)

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,