Skip to content

Commit

Permalink
Switch manual virtual keyboard control to bindings
Browse files Browse the repository at this point in the history
A previous attempt was made to allow manually controlling the virtual
keyboard's state by changing the text-input enabled state from the
compositor. While this can work well for disabling a virtual keyboard,
it's not a great solution to force-enable it.

This patch adds a new method of controlling the virtual keyboard
visibility by introducing the Enable/Disable/AutoVirtualKeyboard key
bindings. While these aren't real keys, binding an action to them allows
manual control of the virtual keyboard through a side-channel the user
is free to pick themselves.

Only modes that have bindings defined can be toggled to, avoiding the
necessity to toggle through a state that isn't available.
  • Loading branch information
chrisduerr committed May 6, 2024
1 parent 429c027 commit 2f3109b
Show file tree
Hide file tree
Showing 8 changed files with 104 additions and 47 deletions.
26 changes: 19 additions & 7 deletions catacomb_ipc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ pub enum IpcMessage {
#[cfg_attr(feature = "clap", clap(long, short))]
mods: Option<Modifiers>,
/// X11 keysym for this binding.
key: ClapKeysym,
key: Keysym,
},
/// Remove a gesture.
UnbindGesture {
Expand All @@ -121,7 +121,7 @@ pub enum IpcMessage {
#[cfg_attr(feature = "clap", clap(long))]
on_press: bool,
/// Base key for this binding.
key: ClapKeysym,
key: Keysym,
/// Program this gesture should spawn.
program: String,
/// Arguments for this gesture's program.
Expand All @@ -136,7 +136,7 @@ pub enum IpcMessage {
#[cfg_attr(feature = "clap", clap(long, short))]
mods: Option<Modifiers>,
/// Base key for this binding.
key: ClapKeysym,
key: Keysym,
},
/// Output power management.
Dpms {
Expand Down Expand Up @@ -406,19 +406,31 @@ impl FromStr for Modifiers {
}

/// Clap wrapper for XKB keysym.
#[derive(Deserialize, Serialize, Copy, Clone, Debug)]
pub struct ClapKeysym(pub u32);
#[derive(Deserialize, Serialize, PartialEq, Eq, Copy, Clone, Debug)]
pub enum Keysym {
EnableVirtualKeyboard,
DisableVirtualKeyboard,
AutoVirtualKeyboard,
Xkb(u32),
}

#[cfg(feature = "clap")]
impl FromStr for ClapKeysym {
impl FromStr for Keysym {
type Err = ClapError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"enablevirtualkeyboard" => return Ok(Self::EnableVirtualKeyboard),
"disablevirtualkeyboard" => return Ok(Self::DisableVirtualKeyboard),
"autovirtualkeyboard" => return Ok(Self::AutoVirtualKeyboard),
_ => (),
}

match xkb::keysym_from_name(s, xkb::KEYSYM_NO_FLAGS).raw() {
keysyms::KEY_NoSymbol => {
Err(ClapError::raw(ClapErrorKind::InvalidValue, format!("invalid keysym {s:?}")))
},
keysym => Ok(Self(keysym)),
keysym => Ok(Self::Xkb(keysym)),
}
}
}
Expand Down
67 changes: 56 additions & 11 deletions src/catacomb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use std::{cmp, env};

use _decoration::zv1::server::zxdg_toplevel_decoration_v1::Mode as DecorationMode;
use _server_decoration::server::org_kde_kwin_server_decoration_manager::Mode as ManagerMode;
use catacomb_ipc::Orientation;
use catacomb_ipc::{Keysym, Orientation};
use smithay::backend::allocator::dmabuf::Dmabuf;
use smithay::backend::renderer::ImportDma;
use smithay::input::keyboard::XkbConfig;
Expand Down Expand Up @@ -37,7 +37,7 @@ use smithay::wayland::fractional_scale::{
use smithay::wayland::idle_inhibit::{IdleInhibitHandler, IdleInhibitManagerState};
use smithay::wayland::idle_notify::{IdleNotifierHandler, IdleNotifierState};
use smithay::wayland::input_method::{
InputMethodHandler, InputMethodManagerState, InputMethodSeat, PopupSurface as ImeSurface,
InputMethodHandler, InputMethodManagerState, PopupSurface as ImeSurface,
};
use smithay::wayland::keyboard_shortcuts_inhibit::{
KeyboardShortcutsInhibitHandler, KeyboardShortcutsInhibitState, KeyboardShortcutsInhibitor,
Expand Down Expand Up @@ -93,7 +93,7 @@ use crate::protocols::single_pixel_buffer::SinglePixelBufferState;
use crate::udev::Udev;
use crate::windows::surface::Surface;
use crate::windows::Windows;
use crate::{delegate_screencopy, delegate_single_pixel_buffer, ipc_server, trace_error};
use crate::{daemon, delegate_screencopy, delegate_single_pixel_buffer, ipc_server, trace_error};

/// Time before xdg_activation tokens are invalidated.
const ACTIVATION_TIMEOUT: Duration = Duration::from_secs(10);
Expand Down Expand Up @@ -361,7 +361,7 @@ impl Catacomb {
// Update surface focus.
let focus = self.windows.focus().map(|(surface, _)| surface);
if focus != self.last_focus {
self.last_focus = focus.clone();
self.last_focus.clone_from(&focus);
self.focus(focus);
}

Expand Down Expand Up @@ -472,18 +472,55 @@ impl Catacomb {

/// Toggle IME force enable/disable.
pub fn toggle_ime_override(&mut self) {
self.ime_override = match self.ime_override {
None => Some(false),
Some(false) => Some(true),
Some(true) => None,
// Find next state and corresponding action.
//
// If one state doesn't have an action, it is skipped. If two states don't have
// an action, we don't do anything.
let VkActions { enable, disable, auto } = self.vk_actions();
let (new_override, (program, args)) = match (self.ime_override, enable, disable, auto) {
(None, _, Some(disable), _) | (Some(true), _, Some(disable), None) => {
(Some(false), disable)
},
(None, Some(enable), ..) | (Some(false), Some(enable), ..) => (Some(true), enable),
(Some(false), _, _, Some(auto)) | (Some(true), _, _, Some(auto)) => (None, auto),
// Ignore toggle if there are less than two states to toggle between.
_ => {
println!("NOTHING TO TOGGLE TO");
return;
},
};

// Execute binding to toggle virtual keyboard override.
if let Err(err) = daemon::spawn(program, args) {
error!("Failed to update virtual keyboard state ({program} {args:?}): {err}");
}

self.ime_override = new_override;

// Ensure gesture handle is updated.
self.windows.set_ime_override(self.ime_override);
}

// Update IME state.
let cseat = self.seat.clone();
cseat.input_method().set_active(self, None, self.ime_override);
/// Get actions for setting virtual keyboard state to enabled/disabled/auto.
fn vk_actions(&self) -> VkActions<'_> {
let mut actions = VkActions::default();

for binding in &self.key_bindings {
let action = (&binding.program, &binding.arguments);
match binding.key {
Keysym::EnableVirtualKeyboard => actions.enable = Some(action),
Keysym::DisableVirtualKeyboard => actions.disable = Some(action),
Keysym::AutoVirtualKeyboard => actions.auto = Some(action),
_ => (),
}

// Stop once all actions were found.
if actions.enable.is_some() && actions.disable.is_some() && actions.auto.is_some() {
break;
}
}

actions
}
}

Expand Down Expand Up @@ -913,3 +950,11 @@ impl FramePacer {
Some(prediction + PREDICTION_PADDING)
}
}

/// Programs for controlling virtual keyboard state.
#[derive(Default)]
struct VkActions<'a> {
enable: Option<(&'a String, &'a Vec<String>)>,
disable: Option<(&'a String, &'a Vec<String>)>,
auto: Option<(&'a String, &'a Vec<String>)>,
}
4 changes: 2 additions & 2 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Compositor configuration.
use catacomb_ipc::{AppIdMatcher, GestureSector, Modifiers};
use catacomb_ipc::{AppIdMatcher, GestureSector, Keysym, Modifiers};

/// User-defined gesture action.
#[derive(Debug)]
Expand All @@ -23,7 +23,7 @@ pub enum GestureBindingAction {
pub struct KeyBinding {
pub app_id: AppIdMatcher,
pub mods: Modifiers,
pub key: u32,
pub key: Keysym,
pub program: String,
pub arguments: Vec<String>,
pub on_press: bool,
Expand Down
4 changes: 2 additions & 2 deletions src/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
use std::time::{Duration, Instant};

use catacomb_ipc::{GestureSector, Modifiers};
use catacomb_ipc::{GestureSector, Keysym, Modifiers};
use smithay::backend::input::{
AbsolutePositionEvent, ButtonState, Event, InputBackend, InputEvent, KeyState,
KeyboardKeyEvent, MouseButton, PointerButtonEvent, TouchEvent as _, TouchSlot,
Expand Down Expand Up @@ -841,7 +841,7 @@ impl Catacomb {
let mut filter_result = FilterResult::Forward;
let pressed = state == KeyState::Pressed;
for key_binding in &catacomb.key_bindings {
if key_binding.key == raw_keysym
if key_binding.key == Keysym::Xkb(raw_keysym)
&& key_binding.mods == mods
&& key_binding.app_id.matches(active_app.as_ref())
&& key_binding.on_press == pressed
Expand Down
16 changes: 12 additions & 4 deletions src/ipc_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use std::io::{BufRead, BufReader, Write};
use std::os::unix::net::{UnixListener, UnixStream};
use std::path::PathBuf;

use catacomb_ipc::{AppIdMatcher, DpmsState, IpcMessage, WindowScale};
use catacomb_ipc::{AppIdMatcher, DpmsState, IpcMessage, Keysym, WindowScale};
use smithay::reexports::calloop::LoopHandle;
use tracing::{error, warn};

Expand Down Expand Up @@ -104,7 +104,16 @@ fn handle_message(buffer: &mut String, mut stream: UnixStream, catacomb: &mut Ca
},
};

let action = GestureBindingAction::Key((key.0, mods.unwrap_or_default()));
// Ignore custom keysyms like virtual keyboard enable/disable.
let key = match key {
Keysym::Xkb(key) => key,
_ => {
warn!("ignoring invalid ipc message: gestures must be bound to real keysyms");
return;
},
};

let action = GestureBindingAction::Key((key, mods.unwrap_or_default()));
let gesture = GestureBinding { app_id, start, end, action };
catacomb.touch_state.user_gestures.push(gesture);
},
Expand All @@ -127,14 +136,13 @@ fn handle_message(buffer: &mut String, mut stream: UnixStream, catacomb: &mut Ca
app_id,
program,
arguments,
key,
mods: mods.unwrap_or_default(),
key: key.0,
};
catacomb.key_bindings.push(binding);
},
IpcMessage::UnbindKey { app_id, mods, key } => {
let mods = mods.unwrap_or_default();
let key = key.0;

catacomb.key_bindings.retain(|binding| {
binding.app_id.base() != app_id || binding.key != key || binding.mods != mods
Expand Down
24 changes: 8 additions & 16 deletions src/orientation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,9 @@ const DEADZONE: f32 = 5.;

pub trait AccelerometerSource {
/// Subscribe to orientation change events.
fn subscribe<'a, F: 'a>(
self,
loop_handle: &LoopHandle<'a, Catacomb>,
fun: F,
) -> RegistrationToken
fn subscribe<'a, F>(self, loop_handle: &LoopHandle<'a, Catacomb>, fun: F) -> RegistrationToken
where
F: FnMut(Orientation, &mut Catacomb);
F: FnMut(Orientation, &mut Catacomb) + 'a;
}

/// Platform-independent accelerometer implementation.
Expand All @@ -49,13 +45,9 @@ impl Accelerometer {
}

impl AccelerometerSource for Accelerometer {
fn subscribe<'a, F: 'a>(
self,
loop_handle: &LoopHandle<'a, Catacomb>,
fun: F,
) -> RegistrationToken
fn subscribe<'a, F>(self, loop_handle: &LoopHandle<'a, Catacomb>, fun: F) -> RegistrationToken
where
F: FnMut(Orientation, &mut Catacomb),
F: FnMut(Orientation, &mut Catacomb) + 'a,
{
match self {
Accelerometer::Sensor(accelerometer) => accelerometer.subscribe(loop_handle, fun),
Expand Down Expand Up @@ -152,13 +144,13 @@ impl SensorAccelerometer {
}

impl AccelerometerSource for SensorAccelerometer {
fn subscribe<'a, F: 'a>(
fn subscribe<'a, F>(
mut self,
loop_handle: &LoopHandle<'a, Catacomb>,
mut fun: F,
) -> RegistrationToken
where
F: FnMut(Orientation, &mut Catacomb),
F: FnMut(Orientation, &mut Catacomb) + 'a,
{
loop_handle
.insert_source(Timer::immediate(), move |_, _, catacomb| {
Expand All @@ -182,13 +174,13 @@ impl AccelerometerSource for SensorAccelerometer {
pub struct DummyAccelerometer;

impl AccelerometerSource for DummyAccelerometer {
fn subscribe<'a, F: 'a>(
fn subscribe<'a, F>(
self,
loop_handle: &LoopHandle<'a, Catacomb>,
mut fun: F,
) -> RegistrationToken
where
F: FnMut(Orientation, &mut Catacomb),
F: FnMut(Orientation, &mut Catacomb) + 'a,
{
loop_handle
.insert_source(Timer::immediate(), move |_, _, catacomb| {
Expand Down
8 changes: 4 additions & 4 deletions src/windows/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,18 +65,18 @@ impl Layouts {
Transaction::Primary(position) => {
if let Some(new_layout) = self.layouts.get(position.index) {
if position.secondary {
layout.primary = new_layout.secondary.clone();
layout.primary.clone_from(&new_layout.secondary);
} else {
layout.primary = new_layout.primary.clone();
layout.primary.clone_from(&new_layout.primary);
}
}
},
Transaction::Secondary(position) => {
if let Some(new_layout) = self.layouts.get(position.index) {
if position.secondary {
layout.secondary = new_layout.secondary.clone();
layout.secondary.clone_from(&new_layout.secondary);
} else {
layout.secondary = new_layout.primary.clone();
layout.secondary.clone_from(&new_layout.primary);
}
}
},
Expand Down
2 changes: 1 addition & 1 deletion src/windows/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -704,7 +704,7 @@ impl<S: Surface + 'static> Window<S> {
) -> Option<Window<PopupSurface>> {
if self.surface.surface() == parent {
// Inherit parent properties.
popup.app_id = self.app_id.clone();
popup.app_id.clone_from(&self.app_id);
popup.visible = self.visible;
popup.scale = self.scale;

Expand Down

0 comments on commit 2f3109b

Please sign in to comment.