config: input device configuration
This commit is contained in:
parent
b5ef2272c0
commit
e5cd473a3a
4 changed files with 906 additions and 477 deletions
|
|
@ -16,6 +16,7 @@ use smithay::{
|
||||||
allocator::{gbm::GbmDevice, Format},
|
allocator::{gbm::GbmDevice, Format},
|
||||||
drm::{DrmDevice, DrmEvent, DrmEventTime, DrmNode, GbmBufferedSurface, NodeType},
|
drm::{DrmDevice, DrmEvent, DrmEventTime, DrmNode, GbmBufferedSurface, NodeType},
|
||||||
egl::{EGLContext, EGLDevice, EGLDisplay},
|
egl::{EGLContext, EGLDevice, EGLDisplay},
|
||||||
|
input::InputEvent,
|
||||||
libinput::{LibinputInputBackend, LibinputSessionInterface},
|
libinput::{LibinputInputBackend, LibinputSessionInterface},
|
||||||
renderer::{
|
renderer::{
|
||||||
multigpu::{egl::EglGlesBackend, GpuManager},
|
multigpu::{egl::EglGlesBackend, GpuManager},
|
||||||
|
|
@ -101,7 +102,10 @@ pub fn init_backend(event_loop: &mut EventLoop<State>, state: &mut State) -> Res
|
||||||
|
|
||||||
let libinput_event_source = event_loop
|
let libinput_event_source = event_loop
|
||||||
.handle()
|
.handle()
|
||||||
.insert_source(libinput_backend, move |event, _, state| {
|
.insert_source(libinput_backend, move |mut event, _, state| {
|
||||||
|
if let &mut InputEvent::DeviceAdded { ref mut device } = &mut event {
|
||||||
|
state.common.config.read_device(device);
|
||||||
|
}
|
||||||
state.process_input_event(event);
|
state.process_input_event(event);
|
||||||
for output in state.common.shell.outputs() {
|
for output in state.common.shell.outputs() {
|
||||||
state.backend.kms().schedule_render(output);
|
state.backend.kms().schedule_render(output);
|
||||||
|
|
|
||||||
476
src/config.rs
476
src/config.rs
|
|
@ -1,476 +0,0 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
shell::{
|
|
||||||
Shell,
|
|
||||||
layout::FocusDirection,
|
|
||||||
},
|
|
||||||
state::BackendData,
|
|
||||||
};
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
pub use smithay::{
|
|
||||||
backend::input::KeyState,
|
|
||||||
utils::{Logical, Physical, Point, Size, Transform},
|
|
||||||
wayland::{
|
|
||||||
output::{Mode, Output},
|
|
||||||
seat::{keysyms as KeySyms, Keysym, ModifiersState as KeyModifiers},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
use std::{cell::RefCell, collections::HashMap, fs::OpenOptions, path::PathBuf};
|
|
||||||
use xkbcommon::xkb;
|
|
||||||
|
|
||||||
pub struct Config {
|
|
||||||
pub static_conf: StaticConfig,
|
|
||||||
pub dynamic_conf: DynamicConfig,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
pub struct StaticConfig {
|
|
||||||
pub key_bindings: HashMap<KeyPattern, Action>,
|
|
||||||
pub workspace_mode: crate::shell::Mode,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct DynamicConfig {
|
|
||||||
outputs: (Option<PathBuf>, OutputsConfig),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
|
||||||
pub struct OutputsConfig {
|
|
||||||
pub config: HashMap<Vec<OutputInfo>, Vec<OutputConfig>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
||||||
pub struct OutputInfo {
|
|
||||||
pub connector: String,
|
|
||||||
pub make: String,
|
|
||||||
pub model: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Output> for OutputInfo {
|
|
||||||
fn from(o: Output) -> OutputInfo {
|
|
||||||
let physical = o.physical_properties();
|
|
||||||
OutputInfo {
|
|
||||||
connector: o.name(),
|
|
||||||
make: physical.make,
|
|
||||||
model: physical.model,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn default_enabled() -> bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
|
|
||||||
pub struct OutputConfig {
|
|
||||||
pub mode: ((i32, i32), Option<u32>),
|
|
||||||
pub vrr: bool,
|
|
||||||
pub scale: f64,
|
|
||||||
#[serde(with = "TransformDef")]
|
|
||||||
pub transform: Transform,
|
|
||||||
pub position: (i32, i32),
|
|
||||||
#[serde(default = "default_enabled")]
|
|
||||||
pub enabled: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for OutputConfig {
|
|
||||||
fn default() -> OutputConfig {
|
|
||||||
OutputConfig {
|
|
||||||
mode: ((0, 0), None),
|
|
||||||
vrr: false,
|
|
||||||
scale: 1.0,
|
|
||||||
transform: Transform::Normal,
|
|
||||||
position: (0, 0),
|
|
||||||
enabled: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl OutputConfig {
|
|
||||||
pub fn mode_size(&self) -> Size<i32, Physical> {
|
|
||||||
self.mode.0.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn mode_refresh(&self) -> u32 {
|
|
||||||
self.mode.1.unwrap_or(60_000)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn output_mode(&self) -> Mode {
|
|
||||||
Mode {
|
|
||||||
size: self.mode_size(),
|
|
||||||
refresh: self.mode_refresh() as i32,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
#[serde(remote = "Transform")]
|
|
||||||
enum TransformDef {
|
|
||||||
Normal,
|
|
||||||
_90,
|
|
||||||
_180,
|
|
||||||
_270,
|
|
||||||
Flipped,
|
|
||||||
Flipped90,
|
|
||||||
Flipped180,
|
|
||||||
Flipped270,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Config {
|
|
||||||
pub fn load() -> Config {
|
|
||||||
let xdg = xdg::BaseDirectories::new().ok();
|
|
||||||
Config {
|
|
||||||
static_conf: Self::load_static(xdg.as_ref()),
|
|
||||||
dynamic_conf: Self::load_dynamic(xdg.as_ref()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn load_static(xdg: Option<&xdg::BaseDirectories>) -> StaticConfig {
|
|
||||||
let mut locations = if let Some(base) = xdg {
|
|
||||||
vec![
|
|
||||||
base.get_config_file("cosmic-comp.ron"),
|
|
||||||
base.get_config_file("cosmic-comp/config.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 {
|
|
||||||
slog_scope::debug!("Trying config location: {}", path.display());
|
|
||||||
if path.exists() {
|
|
||||||
slog_scope::info!("Using config at {}", path.display());
|
|
||||||
return ron::de::from_reader(OpenOptions::new().read(true).open(path).unwrap())
|
|
||||||
.expect("Malformed config file");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
StaticConfig {
|
|
||||||
key_bindings: HashMap::new(),
|
|
||||||
workspace_mode: crate::shell::Mode::global(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn load_dynamic(xdg: Option<&xdg::BaseDirectories>) -> DynamicConfig {
|
|
||||||
let output_path =
|
|
||||||
xdg.and_then(|base| base.place_state_file("cosmic-comp/outputs.ron").ok());
|
|
||||||
let outputs = Self::load_outputs(&output_path);
|
|
||||||
|
|
||||||
DynamicConfig {
|
|
||||||
outputs: (output_path, outputs),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn load_outputs(path: &Option<PathBuf>) -> OutputsConfig {
|
|
||||||
if let Some(path) = path.as_ref() {
|
|
||||||
if path.exists() {
|
|
||||||
match ron::de::from_reader(OpenOptions::new().read(true).open(path).unwrap()) {
|
|
||||||
Ok(config) => return config,
|
|
||||||
Err(err) => {
|
|
||||||
slog_scope::warn!("Failed to read output_config ({}), resetting..", err);
|
|
||||||
if let Err(err) = std::fs::remove_file(path) {
|
|
||||||
slog_scope::error!("Failed to remove output_config {}", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
OutputsConfig {
|
|
||||||
config: HashMap::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read_outputs<'a>(
|
|
||||||
&mut self,
|
|
||||||
outputs: impl Iterator<Item=impl std::borrow::Borrow<Output>>,
|
|
||||||
backend: &mut BackendData,
|
|
||||||
shell: &mut Shell,
|
|
||||||
) {
|
|
||||||
let outputs = outputs.map(|x| x.borrow().clone()).collect::<Vec<_>>();
|
|
||||||
let mut infos = outputs
|
|
||||||
.iter()
|
|
||||||
.cloned()
|
|
||||||
.map(Into::<crate::config::OutputInfo>::into)
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
infos.sort();
|
|
||||||
if let Some(configs) = self.dynamic_conf.outputs().config.get(&infos).cloned() {
|
|
||||||
let mut reset = false;
|
|
||||||
let known_good_configs = outputs
|
|
||||||
.iter()
|
|
||||||
.map(|output| {
|
|
||||||
output
|
|
||||||
.user_data()
|
|
||||||
.get::<RefCell<OutputConfig>>()
|
|
||||||
.unwrap()
|
|
||||||
.borrow()
|
|
||||||
.clone()
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
for (name, output_config) in infos.iter().map(|o| &o.connector).zip(configs.into_iter())
|
|
||||||
{
|
|
||||||
let output = outputs
|
|
||||||
.iter()
|
|
||||||
.find(|o| &o.name() == name)
|
|
||||||
.unwrap()
|
|
||||||
.clone();
|
|
||||||
*output
|
|
||||||
.user_data()
|
|
||||||
.get::<RefCell<OutputConfig>>()
|
|
||||||
.unwrap()
|
|
||||||
.borrow_mut() = output_config;
|
|
||||||
if let Err(err) = backend.apply_config_for_output(&output, false, shell) {
|
|
||||||
slog_scope::warn!(
|
|
||||||
"Failed to set new config for output {}: {}",
|
|
||||||
output.name(),
|
|
||||||
err
|
|
||||||
);
|
|
||||||
reset = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if reset {
|
|
||||||
for (output, output_config) in outputs
|
|
||||||
.clone()
|
|
||||||
.into_iter()
|
|
||||||
.zip(known_good_configs.into_iter())
|
|
||||||
{
|
|
||||||
*output
|
|
||||||
.user_data()
|
|
||||||
.get::<RefCell<OutputConfig>>()
|
|
||||||
.unwrap()
|
|
||||||
.borrow_mut() = output_config;
|
|
||||||
if let Err(err) = backend.apply_config_for_output(&output, false, shell)
|
|
||||||
{
|
|
||||||
slog_scope::error!(
|
|
||||||
"Failed to reset config for output {}: {}",
|
|
||||||
output.name(),
|
|
||||||
err
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn write_outputs<'a>(&mut self, outputs: impl Iterator<Item=impl std::borrow::Borrow<Output>>) {
|
|
||||||
let mut infos = outputs
|
|
||||||
.map(|o| {
|
|
||||||
let o = o.borrow();
|
|
||||||
(
|
|
||||||
Into::<crate::config::OutputInfo>::into(o.clone()),
|
|
||||||
o.user_data()
|
|
||||||
.get::<RefCell<OutputConfig>>()
|
|
||||||
.unwrap()
|
|
||||||
.borrow()
|
|
||||||
.clone(),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect::<Vec<(OutputInfo, OutputConfig)>>();
|
|
||||||
infos.sort_by(|&(ref a, _), &(ref b, _)| a.cmp(b));
|
|
||||||
let (infos, configs) = infos.into_iter().unzip();
|
|
||||||
self
|
|
||||||
.dynamic_conf
|
|
||||||
.outputs_mut()
|
|
||||||
.config
|
|
||||||
.insert(infos, configs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct PersistenceGuard<'a, T: Serialize>(Option<PathBuf>, &'a mut T);
|
|
||||||
|
|
||||||
impl<'a, T: Serialize> std::ops::Deref for PersistenceGuard<'a, T> {
|
|
||||||
type Target = T;
|
|
||||||
fn deref(&self) -> &T {
|
|
||||||
&self.1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, T: Serialize> std::ops::DerefMut for PersistenceGuard<'a, T> {
|
|
||||||
fn deref_mut(&mut self) -> &mut T {
|
|
||||||
&mut self.1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, T: Serialize> Drop for PersistenceGuard<'a, T> {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
if let Some(path) = self.0.as_ref() {
|
|
||||||
let writer = match OpenOptions::new()
|
|
||||||
.create(true)
|
|
||||||
.truncate(true)
|
|
||||||
.write(true)
|
|
||||||
.open(path)
|
|
||||||
{
|
|
||||||
Ok(writer) => writer,
|
|
||||||
Err(err) => {
|
|
||||||
slog_scope::warn!("Failed to persist {}: {}", path.display(), err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if let Err(err) = ron::ser::to_writer_pretty(writer, &self.1, Default::default()) {
|
|
||||||
slog_scope::warn!("Failed to persist {}: {}", path.display(), err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DynamicConfig {
|
|
||||||
pub fn outputs(&self) -> &OutputsConfig {
|
|
||||||
&self.outputs.1
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn outputs_mut<'a>(&'a mut self) -> PersistenceGuard<'a, OutputsConfig> {
|
|
||||||
PersistenceGuard(self.outputs.0.clone(), &mut self.outputs.1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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(FocusDirection),
|
|
||||||
Orientation(crate::shell::layout::Orientation),
|
|
||||||
Fullscreen,
|
|
||||||
Spawn(String),
|
|
||||||
}
|
|
||||||
650
src/config/mod.rs
Normal file
650
src/config/mod.rs
Normal file
|
|
@ -0,0 +1,650 @@
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
shell::{
|
||||||
|
Shell,
|
||||||
|
layout::FocusDirection,
|
||||||
|
},
|
||||||
|
state::BackendData,
|
||||||
|
};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
pub use smithay::{
|
||||||
|
backend::input::KeyState,
|
||||||
|
reexports::input::{AccelProfile, ClickMethod, ScrollMethod, SendEventsMode, TapButtonMap, Device as InputDevice},
|
||||||
|
utils::{Logical, Physical, Point, Size, Transform},
|
||||||
|
wayland::{
|
||||||
|
output::{Mode, Output},
|
||||||
|
seat::{keysyms as KeySyms, Keysym, ModifiersState as KeyModifiers},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use std::{cell::RefCell, collections::HashMap, fs::OpenOptions, path::PathBuf};
|
||||||
|
|
||||||
|
mod types;
|
||||||
|
pub use self::types::*;
|
||||||
|
|
||||||
|
pub struct Config {
|
||||||
|
pub static_conf: StaticConfig,
|
||||||
|
pub dynamic_conf: DynamicConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct StaticConfig {
|
||||||
|
pub key_bindings: HashMap<KeyPattern, Action>,
|
||||||
|
pub workspace_mode: crate::shell::Mode,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DynamicConfig {
|
||||||
|
outputs: (Option<PathBuf>, OutputsConfig),
|
||||||
|
inputs: (Option<PathBuf>, InputsConfig),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
pub struct OutputsConfig {
|
||||||
|
pub config: HashMap<Vec<OutputInfo>, Vec<OutputConfig>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub struct OutputInfo {
|
||||||
|
pub connector: String,
|
||||||
|
pub make: String,
|
||||||
|
pub model: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Output> for OutputInfo {
|
||||||
|
fn from(o: Output) -> OutputInfo {
|
||||||
|
let physical = o.physical_properties();
|
||||||
|
OutputInfo {
|
||||||
|
connector: o.name(),
|
||||||
|
make: physical.make,
|
||||||
|
model: physical.model,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_enabled() -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
|
||||||
|
pub struct OutputConfig {
|
||||||
|
pub mode: ((i32, i32), Option<u32>),
|
||||||
|
pub vrr: bool,
|
||||||
|
pub scale: f64,
|
||||||
|
#[serde(with = "TransformDef")]
|
||||||
|
pub transform: Transform,
|
||||||
|
pub position: (i32, i32),
|
||||||
|
#[serde(default = "default_enabled")]
|
||||||
|
pub enabled: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for OutputConfig {
|
||||||
|
fn default() -> OutputConfig {
|
||||||
|
OutputConfig {
|
||||||
|
mode: ((0, 0), None),
|
||||||
|
vrr: false,
|
||||||
|
scale: 1.0,
|
||||||
|
transform: Transform::Normal,
|
||||||
|
position: (0, 0),
|
||||||
|
enabled: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OutputConfig {
|
||||||
|
pub fn mode_size(&self) -> Size<i32, Physical> {
|
||||||
|
self.mode.0.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mode_refresh(&self) -> u32 {
|
||||||
|
self.mode.1.unwrap_or(60_000)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn output_mode(&self) -> Mode {
|
||||||
|
Mode {
|
||||||
|
size: self.mode_size(),
|
||||||
|
refresh: self.mode_refresh() as i32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
pub struct InputsConfig {
|
||||||
|
xkb: XkbConfig,
|
||||||
|
devices: HashMap<String, InputConfig>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
pub struct InputConfig {
|
||||||
|
state: DeviceState,
|
||||||
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
|
acceleration: Option<AccelConfig>,
|
||||||
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
|
calibration: Option<[f32; 6]>,
|
||||||
|
#[serde(with = "ClickMethodDef")]
|
||||||
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
|
click_method: Option<ClickMethod>,
|
||||||
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
|
disable_while_typing: Option<bool>,
|
||||||
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
|
left_handed: Option<bool>,
|
||||||
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
|
middle_button_emulation: Option<bool>,
|
||||||
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
|
rotation_angle: Option<u32>,
|
||||||
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
|
scroll_config: Option<ScrollConfig>,
|
||||||
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
|
tap_config: Option<TapConfig>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
pub struct AccelConfig {
|
||||||
|
#[serde(with = "AccelProfileDef")]
|
||||||
|
profile: Option<AccelProfile>,
|
||||||
|
speed: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
pub struct ScrollConfig {
|
||||||
|
#[serde(with = "ScrollMethodDef")]
|
||||||
|
method: Option<ScrollMethod>,
|
||||||
|
natural_scroll: Option<bool>,
|
||||||
|
scroll_button: Option<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub enum DeviceState {
|
||||||
|
Enabled,
|
||||||
|
Disabled,
|
||||||
|
DisabledOnExternalMouse,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct TapConfig {
|
||||||
|
enabled: bool,
|
||||||
|
#[serde(with = "TapButtonMapDef")]
|
||||||
|
button_map: Option<TapButtonMap>,
|
||||||
|
drag: bool,
|
||||||
|
drag_lock: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
pub fn load() -> Config {
|
||||||
|
let xdg = xdg::BaseDirectories::new().ok();
|
||||||
|
Config {
|
||||||
|
static_conf: Self::load_static(xdg.as_ref()),
|
||||||
|
dynamic_conf: Self::load_dynamic(xdg.as_ref()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_static(xdg: Option<&xdg::BaseDirectories>) -> StaticConfig {
|
||||||
|
let mut locations = if let Some(base) = xdg {
|
||||||
|
vec![
|
||||||
|
base.get_config_file("cosmic-comp.ron"),
|
||||||
|
base.get_config_file("cosmic-comp/config.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 {
|
||||||
|
slog_scope::debug!("Trying config location: {}", path.display());
|
||||||
|
if path.exists() {
|
||||||
|
slog_scope::info!("Using config at {}", path.display());
|
||||||
|
return ron::de::from_reader(OpenOptions::new().read(true).open(path).unwrap())
|
||||||
|
.expect("Malformed config file");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StaticConfig {
|
||||||
|
key_bindings: HashMap::new(),
|
||||||
|
workspace_mode: crate::shell::Mode::global(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_dynamic(xdg: Option<&xdg::BaseDirectories>) -> DynamicConfig {
|
||||||
|
let output_path =
|
||||||
|
xdg.and_then(|base| base.place_state_file("cosmic-comp/outputs.ron").ok());
|
||||||
|
let outputs = Self::load_outputs(&output_path);
|
||||||
|
|
||||||
|
let input_path =
|
||||||
|
xdg.and_then(|base| base.place_state_file("cosmic-comp/inputs.ron").ok());
|
||||||
|
let inputs = Self::load_inputs(&input_path);
|
||||||
|
|
||||||
|
DynamicConfig {
|
||||||
|
outputs: (output_path, outputs),
|
||||||
|
inputs: (input_path, inputs),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_outputs(path: &Option<PathBuf>) -> OutputsConfig {
|
||||||
|
if let Some(path) = path.as_ref() {
|
||||||
|
if path.exists() {
|
||||||
|
match ron::de::from_reader(OpenOptions::new().read(true).open(path).unwrap()) {
|
||||||
|
Ok(config) => return config,
|
||||||
|
Err(err) => {
|
||||||
|
slog_scope::warn!("Failed to read output_config ({}), resetting..", err);
|
||||||
|
if let Err(err) = std::fs::remove_file(path) {
|
||||||
|
slog_scope::error!("Failed to remove output_config {}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OutputsConfig {
|
||||||
|
config: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_inputs(path: &Option<PathBuf>) -> InputsConfig {
|
||||||
|
if let Some(path) = path.as_ref() {
|
||||||
|
if path.exists() {
|
||||||
|
match ron::de::from_reader(OpenOptions::new().read(true).open(path).unwrap()) {
|
||||||
|
Ok(config) => return config,
|
||||||
|
Err(err) => {
|
||||||
|
slog_scope::warn!("Failed to read input_config ({}), resetting..", err);
|
||||||
|
if let Err(err) = std::fs::remove_file(path) {
|
||||||
|
slog_scope::error!("Failed to remove input_config {}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
InputsConfig {
|
||||||
|
xkb: XkbConfig::default(),
|
||||||
|
devices: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_outputs(
|
||||||
|
&mut self,
|
||||||
|
outputs: impl Iterator<Item=impl std::borrow::Borrow<Output>>,
|
||||||
|
backend: &mut BackendData,
|
||||||
|
shell: &mut Shell,
|
||||||
|
) {
|
||||||
|
let outputs = outputs.map(|x| x.borrow().clone()).collect::<Vec<_>>();
|
||||||
|
let mut infos = outputs
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.map(Into::<crate::config::OutputInfo>::into)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
infos.sort();
|
||||||
|
if let Some(configs) = self.dynamic_conf.outputs().config.get(&infos).cloned() {
|
||||||
|
let mut reset = false;
|
||||||
|
let known_good_configs = outputs
|
||||||
|
.iter()
|
||||||
|
.map(|output| {
|
||||||
|
output
|
||||||
|
.user_data()
|
||||||
|
.get::<RefCell<OutputConfig>>()
|
||||||
|
.unwrap()
|
||||||
|
.borrow()
|
||||||
|
.clone()
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
for (name, output_config) in infos.iter().map(|o| &o.connector).zip(configs.into_iter())
|
||||||
|
{
|
||||||
|
let output = outputs
|
||||||
|
.iter()
|
||||||
|
.find(|o| &o.name() == name)
|
||||||
|
.unwrap()
|
||||||
|
.clone();
|
||||||
|
*output
|
||||||
|
.user_data()
|
||||||
|
.get::<RefCell<OutputConfig>>()
|
||||||
|
.unwrap()
|
||||||
|
.borrow_mut() = output_config;
|
||||||
|
if let Err(err) = backend.apply_config_for_output(&output, false, shell) {
|
||||||
|
slog_scope::warn!(
|
||||||
|
"Failed to set new config for output {}: {}",
|
||||||
|
output.name(),
|
||||||
|
err
|
||||||
|
);
|
||||||
|
reset = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if reset {
|
||||||
|
for (output, output_config) in outputs
|
||||||
|
.clone()
|
||||||
|
.into_iter()
|
||||||
|
.zip(known_good_configs.into_iter())
|
||||||
|
{
|
||||||
|
*output
|
||||||
|
.user_data()
|
||||||
|
.get::<RefCell<OutputConfig>>()
|
||||||
|
.unwrap()
|
||||||
|
.borrow_mut() = output_config;
|
||||||
|
if let Err(err) = backend.apply_config_for_output(&output, false, shell)
|
||||||
|
{
|
||||||
|
slog_scope::error!(
|
||||||
|
"Failed to reset config for output {}: {}",
|
||||||
|
output.name(),
|
||||||
|
err
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_outputs(&mut self, outputs: impl Iterator<Item=impl std::borrow::Borrow<Output>>) {
|
||||||
|
let mut infos = outputs
|
||||||
|
.map(|o| {
|
||||||
|
let o = o.borrow();
|
||||||
|
(
|
||||||
|
Into::<crate::config::OutputInfo>::into(o.clone()),
|
||||||
|
o.user_data()
|
||||||
|
.get::<RefCell<OutputConfig>>()
|
||||||
|
.unwrap()
|
||||||
|
.borrow()
|
||||||
|
.clone(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<Vec<(OutputInfo, OutputConfig)>>();
|
||||||
|
infos.sort_by(|&(ref a, _), &(ref b, _)| a.cmp(b));
|
||||||
|
let (infos, configs) = infos.into_iter().unzip();
|
||||||
|
self
|
||||||
|
.dynamic_conf
|
||||||
|
.outputs_mut()
|
||||||
|
.config
|
||||||
|
.insert(infos, configs);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_device(&mut self, device: &mut InputDevice) {
|
||||||
|
use std::collections::hash_map::Entry;
|
||||||
|
|
||||||
|
let mut inputs = self.dynamic_conf.inputs_mut();
|
||||||
|
match inputs.devices.entry(device.sysname().into()) {
|
||||||
|
Entry::Occupied(entry) => {
|
||||||
|
let config = entry.get();
|
||||||
|
if let Err(err) = match config.state {
|
||||||
|
DeviceState::Enabled => device.config_send_events_set_mode(SendEventsMode::ENABLED),
|
||||||
|
DeviceState::Disabled => device.config_send_events_set_mode(SendEventsMode::DISABLED),
|
||||||
|
DeviceState::DisabledOnExternalMouse => device.config_send_events_set_mode(SendEventsMode::DISABLED_ON_EXTERNAL_MOUSE),
|
||||||
|
} {
|
||||||
|
slog_scope::warn!("Failed to apply mode {:?} for device {:?}: {:?}", config.state, device.name(), err);
|
||||||
|
}
|
||||||
|
if let Some(accel) = config.acceleration.as_ref() {
|
||||||
|
if let Some(profile) = accel.profile {
|
||||||
|
if let Err(err) = device.config_accel_set_profile(profile) {
|
||||||
|
slog_scope::warn!("Failed to apply acceleration profile {:?} for device {:?}: {:?}", profile, device.name(), err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Err(err) = device.config_accel_set_speed(accel.speed) {
|
||||||
|
slog_scope::warn!("Failed to apply acceleration speed {:?} for device {:?}: {:?}", accel.speed, device.name(), err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(matrix) = config.calibration {
|
||||||
|
if let Err(err) = device.config_calibration_set_matrix(matrix) {
|
||||||
|
slog_scope::warn!("Failed to apply calibration matrix {:?} for device {:?}: {:?}", matrix, device.name(), err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(dwt) = config.disable_while_typing {
|
||||||
|
if let Err(err) = device.config_dwt_set_enabled(dwt) {
|
||||||
|
slog_scope::warn!("Failed to apply disable-while-typing {:?} for device {:?}: {:?}", dwt, device.name(), err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(left) = config.left_handed {
|
||||||
|
if let Err(err) = device.config_left_handed_set(left) {
|
||||||
|
slog_scope::warn!("Failed to apply left-handed {:?} for device {:?}: {:?}", left, device.name(), err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(middle) = config.middle_button_emulation {
|
||||||
|
if let Err(err) = device.config_middle_emulation_set_enabled(middle) {
|
||||||
|
slog_scope::warn!("Failed to apply middle-button-emulation {:?} for device {:?}: {:?}", middle, device.name(), err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(angle) = config.rotation_angle {
|
||||||
|
if let Err(err) = device.config_rotation_set_angle(angle) {
|
||||||
|
slog_scope::warn!("Failed to apply rotation-angle {:?} for device {:?}: {:?}", angle, device.name(), err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(scroll) = config.scroll_config.as_ref() {
|
||||||
|
if let Some(method) = scroll.method {
|
||||||
|
if let Err(err) = device.config_scroll_set_method(method) {
|
||||||
|
slog_scope::warn!("Failed to apply scroll method {:?} for device {:?}: {:?}", method, device.name(), err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(natural) = scroll.natural_scroll {
|
||||||
|
if let Err(err) = device.config_scroll_set_natural_scroll_enabled(natural) {
|
||||||
|
slog_scope::warn!("Failed to apply natural scrolling {:?} for device {:?}: {:?}", natural, device.name(), err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(button) = scroll.scroll_button {
|
||||||
|
if let Err(err) = device.config_scroll_set_button(button) {
|
||||||
|
slog_scope::warn!("Failed to apply scroll button {:?} for device {:?}: {:?}", button, device.name(), err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(tap) = config.tap_config.as_ref() {
|
||||||
|
if let Err(err) = device.config_tap_set_enabled(tap.enabled) {
|
||||||
|
slog_scope::warn!("Failed to apply tap-to-click {:?} for device {:?}: {:?}", tap.enabled, device.name(), err);
|
||||||
|
}
|
||||||
|
if let Some(button_map) = tap.button_map {
|
||||||
|
if let Err(err) = device.config_tap_set_button_map(button_map) {
|
||||||
|
slog_scope::warn!("Failed to apply button map {:?} for device {:?}: {:?}", button_map, device.name(), err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Err(err) = device.config_tap_set_drag_enabled(tap.drag) {
|
||||||
|
slog_scope::warn!("Failed to apply tap-drag {:?} for device {:?}: {:?}", tap.drag, device.name(), err);
|
||||||
|
}
|
||||||
|
if let Err(err) = device.config_tap_set_drag_lock_enabled(tap.drag_lock) {
|
||||||
|
slog_scope::warn!("Failed to apply tap-drag-lock {:?} for device {:?}: {:?}", tap.drag_lock, device.name(), err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Entry::Vacant(entry) => {
|
||||||
|
entry.insert(InputConfig {
|
||||||
|
state: match device.config_send_events_mode() {
|
||||||
|
x if x.contains(SendEventsMode::ENABLED) => DeviceState::Enabled,
|
||||||
|
x if x.contains(SendEventsMode::DISABLED_ON_EXTERNAL_MOUSE) => DeviceState::DisabledOnExternalMouse,
|
||||||
|
x if x.contains(SendEventsMode::DISABLED) => DeviceState::Disabled,
|
||||||
|
_ => DeviceState::Disabled,
|
||||||
|
},
|
||||||
|
acceleration: if device.config_accel_is_available() {
|
||||||
|
Some(AccelConfig {
|
||||||
|
profile: device.config_accel_profile(),
|
||||||
|
speed: device.config_accel_speed(),
|
||||||
|
})
|
||||||
|
} else { None },
|
||||||
|
calibration: device.config_calibration_matrix(),
|
||||||
|
click_method: device.config_click_method(),
|
||||||
|
disable_while_typing: if device.config_dwt_is_available() {
|
||||||
|
Some(device.config_dwt_enabled())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
|
left_handed: if device.config_left_handed_is_available() {
|
||||||
|
Some(device.config_left_handed())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
|
middle_button_emulation: if device.config_middle_emulation_is_available() {
|
||||||
|
Some(device.config_middle_emulation_enabled())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
|
rotation_angle: if device.config_rotation_is_available() {
|
||||||
|
Some(device.config_rotation_angle())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
|
scroll_config: if device.config_scroll_methods().iter().any(|x| *x != ScrollMethod::NoScroll) {
|
||||||
|
Some(ScrollConfig {
|
||||||
|
method: device.config_scroll_method(),
|
||||||
|
natural_scroll: if device.config_scroll_has_natural_scroll() {
|
||||||
|
Some(device.config_scroll_natural_scroll_enabled())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
|
scroll_button: if device.config_scroll_method() == Some(ScrollMethod::OnButtonDown) {
|
||||||
|
Some(device.config_scroll_button())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} else { None },
|
||||||
|
tap_config: if device.config_tap_finger_count() > 0 {
|
||||||
|
Some(TapConfig {
|
||||||
|
enabled: device.config_tap_enabled(),
|
||||||
|
button_map: device.config_tap_button_map(),
|
||||||
|
drag: device.config_tap_drag_enabled(),
|
||||||
|
drag_lock: device.config_tap_drag_lock_enabled(),
|
||||||
|
})
|
||||||
|
} else { None },
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PersistenceGuard<'a, T: Serialize>(Option<PathBuf>, &'a mut T);
|
||||||
|
|
||||||
|
impl<'a, T: Serialize> std::ops::Deref for PersistenceGuard<'a, T> {
|
||||||
|
type Target = T;
|
||||||
|
fn deref(&self) -> &T {
|
||||||
|
&self.1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T: Serialize> std::ops::DerefMut for PersistenceGuard<'a, T> {
|
||||||
|
fn deref_mut(&mut self) -> &mut T {
|
||||||
|
&mut self.1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T: Serialize> Drop for PersistenceGuard<'a, T> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if let Some(path) = self.0.as_ref() {
|
||||||
|
let writer = match OpenOptions::new()
|
||||||
|
.create(true)
|
||||||
|
.truncate(true)
|
||||||
|
.write(true)
|
||||||
|
.open(path)
|
||||||
|
{
|
||||||
|
Ok(writer) => writer,
|
||||||
|
Err(err) => {
|
||||||
|
slog_scope::warn!("Failed to persist {}: {}", path.display(), err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if let Err(err) = ron::ser::to_writer_pretty(writer, &self.1, Default::default()) {
|
||||||
|
slog_scope::warn!("Failed to persist {}: {}", path.display(), err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DynamicConfig {
|
||||||
|
pub fn outputs(&self) -> &OutputsConfig {
|
||||||
|
&self.outputs.1
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn outputs_mut<'a>(&'a mut self) -> PersistenceGuard<'a, OutputsConfig> {
|
||||||
|
PersistenceGuard(self.outputs.0.clone(), &mut self.outputs.1)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn inputs(&self) -> &InputsConfig {
|
||||||
|
&self.inputs.1
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn inputs_mut<'a>(&'a mut self) -> PersistenceGuard<'a, InputsConfig> {
|
||||||
|
PersistenceGuard(self.inputs.0.clone(), &mut self.inputs.1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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(FocusDirection),
|
||||||
|
Orientation(crate::shell::layout::Orientation),
|
||||||
|
Fullscreen,
|
||||||
|
Spawn(String),
|
||||||
|
}
|
||||||
251
src/config/types.rs
Normal file
251
src/config/types.rs
Normal file
|
|
@ -0,0 +1,251 @@
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
#![allow(non_snake_case)]
|
||||||
|
|
||||||
|
use super::KeyModifier;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
pub use smithay::{
|
||||||
|
backend::input::KeyState,
|
||||||
|
reexports::input::{AccelProfile, ClickMethod, ScrollMethod, TapButtonMap},
|
||||||
|
utils::{Logical, Physical, Point, Size, Transform},
|
||||||
|
wayland::{
|
||||||
|
output::{Mode, Output},
|
||||||
|
seat::{keysyms as KeySyms, Keysym, ModifiersState as KeyModifiers},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use xkbcommon::xkb;
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
pub struct XkbConfig {
|
||||||
|
pub rules: String,
|
||||||
|
pub model: String,
|
||||||
|
pub layout: String,
|
||||||
|
pub variant: String,
|
||||||
|
pub options: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for XkbConfig {
|
||||||
|
fn default() -> XkbConfig {
|
||||||
|
XkbConfig {
|
||||||
|
rules: String::new(),
|
||||||
|
model: String::new(),
|
||||||
|
layout: String::new(),
|
||||||
|
variant: String::new(),
|
||||||
|
options: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod ClickMethodDef {
|
||||||
|
use smithay::reexports::input::ClickMethod as ClickMethodOrig;
|
||||||
|
use serde::{Deserialize, Serialize, Deserializer, Serializer};
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub enum ClickMethod {
|
||||||
|
ButtonAreas,
|
||||||
|
Clickfinger,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<ClickMethodOrig>, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let o = Option::deserialize(deserializer)?;
|
||||||
|
Ok(o.map(|x| match x {
|
||||||
|
ClickMethod::ButtonAreas => ClickMethodOrig::ButtonAreas,
|
||||||
|
ClickMethod::Clickfinger => ClickMethodOrig::Clickfinger,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn serialize<S>(arg: &Option<ClickMethodOrig>, ser: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer
|
||||||
|
{
|
||||||
|
let arg = match arg {
|
||||||
|
Some(ClickMethodOrig::ButtonAreas) => Some(ClickMethod::ButtonAreas),
|
||||||
|
Some(ClickMethodOrig::Clickfinger) => Some(ClickMethod::Clickfinger),
|
||||||
|
Some(_) | None => None,
|
||||||
|
};
|
||||||
|
Option::serialize(&arg, ser)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod AccelProfileDef {
|
||||||
|
use smithay::reexports::input::AccelProfile as AccelProfileOrig;
|
||||||
|
use serde::{Deserialize, Serialize, Deserializer, Serializer};
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
enum AccelProfile {
|
||||||
|
Flat,
|
||||||
|
Adaptive,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<AccelProfileOrig>, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let o = Option::deserialize(deserializer)?;
|
||||||
|
Ok(o.map(|x| match x {
|
||||||
|
AccelProfile::Flat => AccelProfileOrig::Flat,
|
||||||
|
AccelProfile::Adaptive => AccelProfileOrig::Adaptive,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn serialize<S>(arg: &Option<AccelProfileOrig>, ser: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer
|
||||||
|
{
|
||||||
|
let arg = match arg {
|
||||||
|
Some(AccelProfileOrig::Flat) => Some(AccelProfile::Flat),
|
||||||
|
Some(AccelProfileOrig::Adaptive) => Some(AccelProfile::Adaptive),
|
||||||
|
Some(_) | None => None,
|
||||||
|
};
|
||||||
|
Option::serialize(&arg, ser)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod ScrollMethodDef {
|
||||||
|
use smithay::reexports::input::ScrollMethod as ScrollMethodOrig;
|
||||||
|
use serde::{Deserialize, Serialize, Deserializer, Serializer};
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub enum ScrollMethod {
|
||||||
|
NoScroll,
|
||||||
|
TwoFinger,
|
||||||
|
Edge,
|
||||||
|
OnButtonDown,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<ScrollMethodOrig>, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let o = Option::deserialize(deserializer)?;
|
||||||
|
Ok(o.map(|x| match x {
|
||||||
|
ScrollMethod::NoScroll => ScrollMethodOrig::NoScroll,
|
||||||
|
ScrollMethod::TwoFinger => ScrollMethodOrig::TwoFinger,
|
||||||
|
ScrollMethod::Edge => ScrollMethodOrig::Edge,
|
||||||
|
ScrollMethod::OnButtonDown => ScrollMethodOrig::OnButtonDown,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn serialize<S>(arg: &Option<ScrollMethodOrig>, ser: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer
|
||||||
|
{
|
||||||
|
let arg = match arg {
|
||||||
|
Some(ScrollMethodOrig::NoScroll) => Some(ScrollMethod::NoScroll),
|
||||||
|
Some(ScrollMethodOrig::TwoFinger) => Some(ScrollMethod::TwoFinger),
|
||||||
|
Some(ScrollMethodOrig::Edge) => Some(ScrollMethod::Edge),
|
||||||
|
Some(ScrollMethodOrig::OnButtonDown) => Some(ScrollMethod::OnButtonDown),
|
||||||
|
Some(_) | None => None,
|
||||||
|
};
|
||||||
|
Option::serialize(&arg, ser)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
#[serde(remote = "Transform")]
|
||||||
|
pub enum TransformDef {
|
||||||
|
Normal,
|
||||||
|
_90,
|
||||||
|
_180,
|
||||||
|
_270,
|
||||||
|
Flipped,
|
||||||
|
Flipped90,
|
||||||
|
Flipped180,
|
||||||
|
Flipped270,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod TapButtonMapDef {
|
||||||
|
use smithay::reexports::input::TapButtonMap as TapButtonMapOrig;
|
||||||
|
use serde::{Deserialize, Serialize, Deserializer, Serializer};
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub enum TapButtonMap {
|
||||||
|
LeftRightMiddle,
|
||||||
|
LeftMiddleRight,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<TapButtonMapOrig>, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let o = Option::deserialize(deserializer)?;
|
||||||
|
Ok(o.map(|x| match x {
|
||||||
|
TapButtonMap::LeftRightMiddle => TapButtonMapOrig::LeftRightMiddle,
|
||||||
|
TapButtonMap::LeftMiddleRight => TapButtonMapOrig::LeftMiddleRight,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn serialize<S>(arg: &Option<TapButtonMapOrig>, ser: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer
|
||||||
|
{
|
||||||
|
let arg = match arg {
|
||||||
|
Some(TapButtonMapOrig::LeftRightMiddle) => Some(TapButtonMap::LeftRightMiddle),
|
||||||
|
Some(TapButtonMapOrig::LeftMiddleRight) => Some(TapButtonMap::LeftMiddleRight),
|
||||||
|
Some(_) | None => None,
|
||||||
|
};
|
||||||
|
Option::serialize(&arg, ser)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[serde(transparent)]
|
||||||
|
pub 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: KeyModifier| {
|
||||||
|
modis += modi;
|
||||||
|
modis
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub 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)]
|
||||||
|
pub 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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue