Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ serde_json = "1"
thiserror = "2.0.18"
jiff = "0.2"
tiny-skia = "0.11"
toml = { version = "0.9", default-features = false, features = ["parse", "serde"] }
tracing = { version = "0.1.44", features = [
"max_level_debug",
"release_max_level_info",
Expand Down
4 changes: 3 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ TARGET_BIN="$(DESTDIR)$(bindir)/$(BINARY)"

KEYBINDINGS_CONF="$(DESTDIR)$(sharedir)/cosmic/com.system76.CosmicSettings.Shortcuts/v1/defaults"
TILING_EXCEPTIONS_CONF="$(DESTDIR)$(sharedir)/cosmic/com.system76.CosmicSettings.WindowRules/v1/tiling_exception_defaults"
EI_CLIENTS_EXAMPLE="$(DESTDIR)$(sharedir)/doc/cosmic-comp/ei-clients.toml.example"

all: extract-vendor
cargo build $(ARGS)
Expand Down Expand Up @@ -52,6 +53,7 @@ install:
install -Dm0755 "$(CARGO_TARGET_DIR)/$(TARGET)/$(BINARY)" "$(TARGET_BIN)"
install -Dm0644 "data/keybindings.ron" "$(KEYBINDINGS_CONF)"
install -Dm0644 "data/tiling-exceptions.ron" "$(TILING_EXCEPTIONS_CONF)"
install -Dm0644 "data/ei-clients.toml.example" "$(EI_CLIENTS_EXAMPLE)"

install-bare-session: install
install -Dm0644 "data/cosmic.desktop" "$(DESTDIR)$(sharedir)/wayland-sessions/cosmic.desktop"
Expand All @@ -61,7 +63,7 @@ install-bare-session: install
install -Dm0755 "data/cosmic-service" "$(DESTDIR)/$(bindir)/cosmic-service"

uninstall:
rm "$(TARGET_BIN)" "$(KEYBINDINGS_CONF)"
rm "$(TARGET_BIN)" "$(KEYBINDINGS_CONF)" "$(EI_CLIENTS_EXAMPLE)"

uninstall-bare-session:
rm "$(DESTDIR)$(sharedir)/wayland-sessions/cosmic.desktop"
48 changes: 48 additions & 0 deletions data/ei-clients.toml.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Additional clients allowed on the compositor's D-Bus interfaces:
#
# [[ei.client]] "com.system76.CosmicComp.Ei" open an EI
# (libei) sender socket, i.e. synthesize
# keyboard, pointer and touch input.
# [[a11y_keyboard_monitor.client]] "org.freedesktop.a11y.KeyboardMonitor"
# observe and grab keys. Grant this only to
# assistive tech you trust with every
# keystroke typed in the session, passwords
# included.
#
# Copy this file to a drop-in directory to activate it.
# Use `install -m0644`. The compositor ignores a file left group-writable.
# The compositor reads two drop-in directories, in this order:
#
# /usr/share/cosmic-comp/ei-clients.d/*.toml # for packages
# /etc/cosmic-comp/ei-clients.d/*.toml # for local administration
#
# It applies the files in order of their file name across both directories. A
# file in /etc replaces a file of the same name under /usr/share, so you can mask
# a grant shipped by a package with an empty file of that name in /etc.
#
# The compositor picks up changes at start-up only.
# Log out and back in after editing.
#
# The compositor always allows its own built-in clients, which need no entry
# here: the XDG desktop portal implementation and the on-screen keyboard for
# [ei], Orca for [a11y_keyboard_monitor]. Entries here add access. They cannot
# narrow or remove a built-in.

# The compositor identifies a client by a well-known bus name it owns on the
# session bus. The client must request that name before calling GetSenderSocket.
[[ei.client]]
bus_name = "org.example.MyRemoteTool"

# `device_types` limits what the client may ask for. Omit it and the client may
# request every type. Valid values are "keyboard", "pointer" and "touchscreen".
#
# The client below can move the pointer, but cannot inject keystrokes.
[[ei.client]]
bus_name = "com.example.Kiosk"
device_types = ["pointer"]

# You name a keyboard monitor the same way. There is no `device_types` here.
# The interface carries no per-device policy, so a client watches every key
# or nothing.
[[a11y_keyboard_monitor.client]]
bus_name = "org.example.MyScreenReader"
32 changes: 16 additions & 16 deletions src/dbus/a11y_keyboard_monitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,9 @@ use std::{
};
use tracing::debug;
use xkbcommon::xkb::Keysym;
use zbus::{
message::Header,
names::{UniqueName, WellKnownName},
object_server::SignalEmitter,
};

use super::name_owners::NameOwners;
use zbus::{message::Header, names::UniqueName, object_server::SignalEmitter};

static ALLOWED_NAMES: &[WellKnownName] = &[WellKnownName::from_static_str_unchecked(
"org.gnome.Orca.KeyboardMonitor",
)];
use super::{client_allow_list::NameAllowList, name_owners::NameOwners};

// As defined in at-spi2-core
const ATSPI_DEVICE_A11Y_MANAGER_VIRTUAL_MOD_START: u32 = 15;
Expand Down Expand Up @@ -76,19 +68,22 @@ pub struct A11yKeyboardMonitorState {
active_virtual_mods: HashSet<Keysym>,
conn: zbus::Connection,
name_owners: NameOwners,
allow_list: NameAllowList,
}

impl A11yKeyboardMonitorState {
pub async fn new(
conn: &zbus::Connection,
name_owners: &NameOwners,
executor: &calloop::futures::Scheduler<()>,
allow_list: NameAllowList,
) -> zbus::Result<Self> {
let clients = Arc::new(Mutex::new(Clients::default()));

let keyboard_monitor = KeyboardMonitor {
clients: clients.clone(),
name_owners: name_owners.clone(),
allow_list: allow_list.clone(),
};
conn.object_server()
.at("/org/freedesktop/a11y/Manager", keyboard_monitor)
Expand All @@ -101,6 +96,7 @@ impl A11yKeyboardMonitorState {
active_virtual_mods: HashSet::new(),
conn: conn.clone(),
name_owners: name_owners.clone(),
allow_list,
})
}

Expand Down Expand Up @@ -188,22 +184,26 @@ impl A11yKeyboardMonitorState {
pub fn refresh(&mut self) {
// Remove clients and associated grabs when unique names are no longer
// present on bus, or no longer hold approved name on bus.
self.clients
.lock()
.unwrap()
.0
.retain(|k, _| self.name_owners.check_owner_no_poll(k, ALLOWED_NAMES))
self.clients.lock().unwrap().0.retain(|k, _| {
self.name_owners
.check_owner_no_poll(k, self.allow_list.names())
})
}
}

struct KeyboardMonitor {
clients: Arc<Mutex<Clients>>,
name_owners: NameOwners,
allow_list: NameAllowList,
}

impl KeyboardMonitor {
async fn check_sender_allowed(&self, sender: &UniqueName<'_>) -> zbus::fdo::Result<()> {
if self.name_owners.check_owner(sender, ALLOWED_NAMES).await {
if self
.name_owners
.check_owner(sender, self.allow_list.names())
.await
{
Ok(())
} else {
Err(zbus::fdo::Error::AccessDenied("Access denied".to_string()))
Expand Down
Loading
Loading