Compare commits

...

9 Commits

Author SHA1 Message Date
Ferdinand Schober
df85f0baf8 macos: implement client side modifier events
closes #198
closes #199
2024-10-25 22:59:25 +02:00
Ferdinand Schober
75b790ec2e propagate event tap creation error (#218) 2024-10-25 16:31:15 +02:00
Emile Akbarzadeh
9f52a2ac93 Add default.nix file to main and update readme (#211)
Co-authored-by: Emile Akbarzadeh <emile@dromeda.com.au>
2024-10-25 16:20:15 +02:00
Ferdinand Schober
4856199153 fix windows ci 2024-10-25 15:12:20 +02:00
虢豳
24e7a1d07f Add desktop and icon (#212)
* nix flake update

* nix: add desktop and icon

closes #210
2024-10-07 12:05:07 +02:00
Ferdinand Schober
555fbfeb79 formatting 2024-10-05 21:48:49 +02:00
Ferdinand Schober
6191216873 windows: fix panic when recreating input-capture
RegisterWindowClassW fails when called again
2024-10-05 21:47:12 +02:00
Ferdinand Schober
5b1dc4ccf8 reference count capture (#209)
* reference count capture

Multiple captures can now be created at the same position.
Captures at the same position are reference counted.

* update testcase

will be required by #200 / #164
2024-10-05 21:22:28 +02:00
Ferdinand Schober
ab1b45ff45 macos: fix key-release with repeat logic (#206) 2024-10-04 18:21:40 +02:00
19 changed files with 505 additions and 318 deletions

View File

@@ -55,7 +55,9 @@ jobs:
# choco install msys2 # choco install msys2
# choco install visualstudio2022-workload-vctools # choco install visualstudio2022-workload-vctools
# choco install pkgconfiglite # choco install pkgconfiglite
pipx install gvsbuild py -m venv .venv
.venv\Scripts\activate.ps1
py -m pip install gvsbuild
# see https://github.com/wingtk/gvsbuild/pull/1004 # see https://github.com/wingtk/gvsbuild/pull/1004
Move-Item "C:\Program Files\Git\usr\bin" "C:\Program Files\Git\usr\notbin" Move-Item "C:\Program Files\Git\usr\bin" "C:\Program Files\Git\usr\notbin"
Move-Item "C:\Program Files\Git\bin" "C:\Program Files\Git\notbin" Move-Item "C:\Program Files\Git\bin" "C:\Program Files\Git\notbin"

View File

@@ -66,7 +66,9 @@ jobs:
# choco install msys2 # choco install msys2
# choco install visualstudio2022-workload-vctools # choco install visualstudio2022-workload-vctools
# choco install pkgconfiglite # choco install pkgconfiglite
pipx install gvsbuild py -m venv .venv
.venv\Scripts\activate.ps1
py -m pip install gvsbuild
# see https://github.com/wingtk/gvsbuild/pull/1004 # see https://github.com/wingtk/gvsbuild/pull/1004
Move-Item "C:\Program Files\Git\usr\bin" "C:\Program Files\Git\usr\notbin" Move-Item "C:\Program Files\Git\usr\bin" "C:\Program Files\Git\usr\notbin"
Move-Item "C:\Program Files\Git\bin" "C:\Program Files\Git\notbin" Move-Item "C:\Program Files\Git\bin" "C:\Program Files\Git\notbin"

View File

@@ -51,7 +51,9 @@ jobs:
# choco install msys2 # choco install msys2
# choco install visualstudio2022-workload-vctools # choco install visualstudio2022-workload-vctools
# choco install pkgconfiglite # choco install pkgconfiglite
pipx install gvsbuild py -m venv .venv
.venv\Scripts\activate.ps1
py -m pip install gvsbuild
# see https://github.com/wingtk/gvsbuild/pull/1004 # see https://github.com/wingtk/gvsbuild/pull/1004
Move-Item "C:\Program Files\Git\usr\bin" "C:\Program Files\Git\usr\notbin" Move-Item "C:\Program Files\Git\usr\bin" "C:\Program Files\Git\usr\notbin"
Move-Item "C:\Program Files\Git\bin" "C:\Program Files\Git\notbin" Move-Item "C:\Program Files\Git\bin" "C:\Program Files\Git\notbin"

1
Cargo.lock generated
View File

@@ -1226,6 +1226,7 @@ version = "0.2.1"
dependencies = [ dependencies = [
"ashpd", "ashpd",
"async-trait", "async-trait",
"bitflags 2.6.0",
"core-graphics", "core-graphics",
"futures", "futures",
"input-event", "input-event",

View File

@@ -94,6 +94,13 @@ paru -S lan-mouse-bin
- nixpkgs: [search.nixos.org](https://search.nixos.org/packages?channel=unstable&show=lan-mouse&from=0&size=50&sort=relevance&type=packages&query=lan-mouse) - nixpkgs: [search.nixos.org](https://search.nixos.org/packages?channel=unstable&show=lan-mouse&from=0&size=50&sort=relevance&type=packages&query=lan-mouse)
- flake: [README.md](./nix/README.md) - flake: [README.md](./nix/README.md)
### Building from source with Nix
In the root of the project to build Lan Mouse, run
```sh
nix-build
```
You can find the executable in `result/bin/lan-mouse` .
### Manual Installation ### Manual Installation

3
default.nix Normal file
View File

@@ -0,0 +1,3 @@
{ pkgs ? import <nixpkgs> { }
}:
pkgs.callPackage nix/default.nix { }

46
flake.lock generated
View File

@@ -1,30 +1,12 @@
{ {
"nodes": { "nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1705309234,
"narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1716293225, "lastModified": 1728018373,
"narHash": "sha256-pU9ViBVE3XYb70xZx+jK6SEVphvt7xMTbm6yDIF4xPs=", "narHash": "sha256-NOiTvBbRLIOe5F6RbHaAh6++BNjsb149fGZd1T4+KBg=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "3eaeaeb6b1e08a016380c279f8846e0bd8808916", "rev": "bc947f541ae55e999ffdb4013441347d83b00feb",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -42,17 +24,16 @@
}, },
"rust-overlay": { "rust-overlay": {
"inputs": { "inputs": {
"flake-utils": "flake-utils",
"nixpkgs": [ "nixpkgs": [
"nixpkgs" "nixpkgs"
] ]
}, },
"locked": { "locked": {
"lastModified": 1716257780, "lastModified": 1728181869,
"narHash": "sha256-R+NjvJzKEkTVCmdrKRfPE4liX/KMGVqGUwwS5H8ET8A=", "narHash": "sha256-sQXHXsjIcGEoIHkB+RO6BZdrPfB+43V1TEpyoWRI3ww=",
"owner": "oxalica", "owner": "oxalica",
"repo": "rust-overlay", "repo": "rust-overlay",
"rev": "4e5e3d2c5c9b2721bd266f9e43c14e96811b89d2", "rev": "cd46aa3906c14790ef5cbe278d9e54f2c38f95c0",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -60,21 +41,6 @@
"repo": "rust-overlay", "repo": "rust-overlay",
"type": "github" "type": "github"
} }
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
} }
}, },
"root": "root", "root": "root",

View File

@@ -8,7 +8,7 @@ use futures_core::Stream;
use input_event::PointerEvent; use input_event::PointerEvent;
use tokio::time::{self, Instant, Interval}; use tokio::time::{self, Instant, Interval};
use super::{Capture, CaptureError, CaptureEvent, CaptureHandle, Position}; use super::{Capture, CaptureError, CaptureEvent, Position};
pub struct DummyInputCapture { pub struct DummyInputCapture {
start: Option<Instant>, start: Option<Instant>,
@@ -34,11 +34,11 @@ impl Default for DummyInputCapture {
#[async_trait] #[async_trait]
impl Capture for DummyInputCapture { impl Capture for DummyInputCapture {
async fn create(&mut self, _handle: CaptureHandle, _pos: Position) -> Result<(), CaptureError> { async fn create(&mut self, _pos: Position) -> Result<(), CaptureError> {
Ok(()) Ok(())
} }
async fn destroy(&mut self, _handle: CaptureHandle) -> Result<(), CaptureError> { async fn destroy(&mut self, _pos: Position) -> Result<(), CaptureError> {
Ok(()) Ok(())
} }
@@ -55,7 +55,7 @@ const FREQUENCY_HZ: f64 = 1.0;
const RADIUS: f64 = 100.0; const RADIUS: f64 = 100.0;
impl Stream for DummyInputCapture { impl Stream for DummyInputCapture {
type Item = Result<(CaptureHandle, CaptureEvent), CaptureError>; type Item = Result<(Position, CaptureEvent), CaptureError>;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> { fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let current = ready!(self.interval.poll_tick(cx)); let current = ready!(self.interval.poll_tick(cx));
@@ -81,6 +81,6 @@ impl Stream for DummyInputCapture {
})) }))
} }
}; };
Poll::Ready(Some(Ok((0, event)))) Poll::Ready(Some(Ok((Position::Left, event))))
} }
} }

View File

@@ -93,7 +93,7 @@ pub enum CaptureCreationError {
#[error("error creating windows capture backend")] #[error("error creating windows capture backend")]
Windows, Windows,
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
#[error("error creating macos capture backend")] #[error("error creating macos capture backend: `{0}`")]
MacOS(#[from] MacosCaptureCreationError), MacOS(#[from] MacosCaptureCreationError),
} }
@@ -165,6 +165,9 @@ pub enum X11InputCaptureCreationError {
pub enum MacosCaptureCreationError { pub enum MacosCaptureCreationError {
#[error("event source creation failed!")] #[error("event source creation failed!")]
EventSourceCreation, EventSourceCreation,
#[cfg(target_os = "macos")]
#[error("event tap creation failed")]
EventTapCreation,
#[error("failed to set CG Cursor property")] #[error("failed to set CG Cursor property")]
CGCursorProperty, CGCursorProperty,
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]

View File

@@ -1,4 +1,9 @@
use std::{collections::HashSet, fmt::Display, task::Poll}; use std::{
collections::{HashMap, HashSet, VecDeque},
fmt::Display,
mem::swap,
task::{ready, Poll},
};
use async_trait::async_trait; use async_trait::async_trait;
use futures::StreamExt; use futures::StreamExt;
@@ -112,19 +117,48 @@ impl Display for Backend {
} }
pub struct InputCapture { pub struct InputCapture {
/// capture backend
capture: Box<dyn Capture>, capture: Box<dyn Capture>,
/// keys pressed by active capture
pressed_keys: HashSet<scancode::Linux>, pressed_keys: HashSet<scancode::Linux>,
/// map from position to ids
position_map: HashMap<Position, Vec<CaptureHandle>>,
/// map from id to position
id_map: HashMap<CaptureHandle, Position>,
/// pending events
pending: VecDeque<(CaptureHandle, CaptureEvent)>,
} }
impl InputCapture { impl InputCapture {
/// create a new client with the given id /// create a new client with the given id
pub async fn create(&mut self, id: CaptureHandle, pos: Position) -> Result<(), CaptureError> { pub async fn create(&mut self, id: CaptureHandle, pos: Position) -> Result<(), CaptureError> {
self.capture.create(id, pos).await if let Some(v) = self.position_map.get_mut(&pos) {
v.push(id);
Ok(())
} else {
self.position_map.insert(pos, vec![id]);
self.id_map.insert(id, pos);
self.capture.create(pos).await
}
} }
/// destroy the client with the given id, if it exists /// destroy the client with the given id, if it exists
pub async fn destroy(&mut self, id: CaptureHandle) -> Result<(), CaptureError> { pub async fn destroy(&mut self, id: CaptureHandle) -> Result<(), CaptureError> {
self.capture.destroy(id).await if let Some(pos) = self.id_map.remove(&id) {
let destroy = if let Some(v) = self.position_map.get_mut(&pos) {
v.retain(|&i| i != id);
// we were the last id registered at this position
v.is_empty()
} else {
// nothing to destroy
false
};
if destroy {
self.position_map.remove(&pos);
self.capture.destroy(pos).await?;
}
}
Ok(())
} }
/// release mouse /// release mouse
@@ -143,6 +177,9 @@ impl InputCapture {
let capture = create(backend).await?; let capture = create(backend).await?;
Ok(Self { Ok(Self {
capture, capture,
id_map: Default::default(),
pending: Default::default(),
position_map: Default::default(),
pressed_keys: HashSet::new(), pressed_keys: HashSet::new(),
}) })
} }
@@ -170,29 +207,65 @@ impl Stream for InputCapture {
mut self: std::pin::Pin<&mut Self>, mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>, cx: &mut std::task::Context<'_>,
) -> Poll<Option<Self::Item>> { ) -> Poll<Option<Self::Item>> {
match self.capture.poll_next_unpin(cx) { if let Some(e) = self.pending.pop_front() {
Poll::Ready(e) => { return Poll::Ready(Some(Ok(e)));
if let Some(Ok(( }
_,
CaptureEvent::Input(Event::Keyboard(KeyboardEvent::Key { key, state, .. })), // ready
))) = e let event = ready!(self.capture.poll_next_unpin(cx));
// stream closed
let event = match event {
Some(e) => e,
None => return Poll::Ready(None),
};
// error occurred
let (pos, event) = match event {
Ok(e) => e,
Err(e) => return Poll::Ready(Some(Err(e))),
};
// handle key presses
if let CaptureEvent::Input(Event::Keyboard(KeyboardEvent::Key { key, state, .. })) = event {
self.update_pressed_keys(key, state);
}
let len = self
.position_map
.get(&pos)
.map(|ids| ids.len())
.unwrap_or(0);
match len {
0 => Poll::Pending,
1 => Poll::Ready(Some(Ok((
self.position_map.get(&pos).expect("no id")[0],
event,
)))),
_ => {
let mut position_map = HashMap::new();
swap(&mut self.position_map, &mut position_map);
{ {
self.update_pressed_keys(key, state); for &id in position_map.get(&pos).expect("position") {
self.pending.push_back((id, event));
}
} }
Poll::Ready(e) swap(&mut self.position_map, &mut position_map);
Poll::Ready(Some(Ok(self.pending.pop_front().expect("event"))))
} }
Poll::Pending => Poll::Pending,
} }
} }
} }
#[async_trait] #[async_trait]
trait Capture: Stream<Item = Result<(CaptureHandle, CaptureEvent), CaptureError>> + Unpin { trait Capture: Stream<Item = Result<(Position, CaptureEvent), CaptureError>> + Unpin {
/// create a new client with the given id /// create a new client with the given id
async fn create(&mut self, id: CaptureHandle, pos: Position) -> Result<(), CaptureError>; async fn create(&mut self, pos: Position) -> Result<(), CaptureError>;
/// destroy the client with the given id, if it exists /// destroy the client with the given id, if it exists
async fn destroy(&mut self, id: CaptureHandle) -> Result<(), CaptureError>; async fn destroy(&mut self, pos: Position) -> Result<(), CaptureError>;
/// release mouse /// release mouse
async fn release(&mut self) -> Result<(), CaptureError>; async fn release(&mut self) -> Result<(), CaptureError>;
@@ -204,14 +277,14 @@ trait Capture: Stream<Item = Result<(CaptureHandle, CaptureEvent), CaptureError>
async fn create_backend( async fn create_backend(
backend: Backend, backend: Backend,
) -> Result< ) -> Result<
Box<dyn Capture<Item = Result<(CaptureHandle, CaptureEvent), CaptureError>>>, Box<dyn Capture<Item = Result<(Position, CaptureEvent), CaptureError>>>,
CaptureCreationError, CaptureCreationError,
> { > {
match backend { match backend {
#[cfg(all(unix, feature = "libei", not(target_os = "macos")))] #[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
Backend::InputCapturePortal => Ok(Box::new(libei::LibeiInputCapture::new().await?)), Backend::InputCapturePortal => Ok(Box::new(libei::LibeiInputCapture::new().await?)),
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))] #[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
Backend::LayerShell => Ok(Box::new(wayland::WaylandInputCapture::new()?)), Backend::LayerShell => Ok(Box::new(wayland::LayerShellInputCapture::new()?)),
#[cfg(all(unix, feature = "x11", not(target_os = "macos")))] #[cfg(all(unix, feature = "x11", not(target_os = "macos")))]
Backend::X11 => Ok(Box::new(x11::X11InputCapture::new()?)), Backend::X11 => Ok(Box::new(x11::X11InputCapture::new()?)),
#[cfg(windows)] #[cfg(windows)]
@@ -225,7 +298,7 @@ async fn create_backend(
async fn create( async fn create(
backend: Option<Backend>, backend: Option<Backend>,
) -> Result< ) -> Result<
Box<dyn Capture<Item = Result<(CaptureHandle, CaptureEvent), CaptureError>>>, Box<dyn Capture<Item = Result<(Position, CaptureEvent), CaptureError>>>,
CaptureCreationError, CaptureCreationError,
> { > {
if let Some(backend) = backend { if let Some(backend) = backend {

View File

@@ -40,7 +40,7 @@ use crate::CaptureEvent;
use super::{ use super::{
error::{CaptureError, LibeiCaptureCreationError, ReisConvertEventStreamError}, error::{CaptureError, LibeiCaptureCreationError, ReisConvertEventStreamError},
Capture as LanMouseInputCapture, CaptureHandle, Position, Capture as LanMouseInputCapture, Position,
}; };
/* there is a bug in xdg-remote-desktop-portal-gnome / mutter that /* there is a bug in xdg-remote-desktop-portal-gnome / mutter that
@@ -50,15 +50,15 @@ use super::{
/// events that necessitate restarting the capture session /// events that necessitate restarting the capture session
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
enum LibeiNotifyEvent { enum LibeiNotifyEvent {
Create(CaptureHandle, Position), Create(Position),
Destroy(CaptureHandle), Destroy(Position),
} }
#[allow(dead_code)] #[allow(dead_code)]
pub struct LibeiInputCapture<'a> { pub struct LibeiInputCapture<'a> {
input_capture: Pin<Box<InputCapture<'a>>>, input_capture: Pin<Box<InputCapture<'a>>>,
capture_task: JoinHandle<Result<(), CaptureError>>, capture_task: JoinHandle<Result<(), CaptureError>>,
event_rx: Receiver<(CaptureHandle, CaptureEvent)>, event_rx: Receiver<(Position, CaptureEvent)>,
notify_capture: Sender<LibeiNotifyEvent>, notify_capture: Sender<LibeiNotifyEvent>,
notify_release: Arc<Notify>, notify_release: Arc<Notify>,
cancellation_token: CancellationToken, cancellation_token: CancellationToken,
@@ -117,13 +117,13 @@ impl From<ICBarrier> for Barrier {
fn select_barriers( fn select_barriers(
zones: &Zones, zones: &Zones,
clients: &[(CaptureHandle, Position)], clients: &[Position],
next_barrier_id: &mut u32, next_barrier_id: &mut u32,
) -> (Vec<ICBarrier>, HashMap<BarrierID, CaptureHandle>) { ) -> (Vec<ICBarrier>, HashMap<BarrierID, Position>) {
let mut client_for_barrier = HashMap::new(); let mut pos_for_barrier = HashMap::new();
let mut barriers: Vec<ICBarrier> = vec![]; let mut barriers: Vec<ICBarrier> = vec![];
for (handle, pos) in clients { for pos in clients {
let mut client_barriers = zones let mut client_barriers = zones
.regions() .regions()
.iter() .iter()
@@ -131,21 +131,21 @@ fn select_barriers(
let id = *next_barrier_id; let id = *next_barrier_id;
*next_barrier_id = id + 1; *next_barrier_id = id + 1;
let position = pos_to_barrier(r, *pos); let position = pos_to_barrier(r, *pos);
client_for_barrier.insert(id, *handle); pos_for_barrier.insert(id, *pos);
ICBarrier::new(id, position) ICBarrier::new(id, position)
}) })
.collect(); .collect();
barriers.append(&mut client_barriers); barriers.append(&mut client_barriers);
} }
(barriers, client_for_barrier) (barriers, pos_for_barrier)
} }
async fn update_barriers( async fn update_barriers(
input_capture: &InputCapture<'_>, input_capture: &InputCapture<'_>,
session: &Session<'_, InputCapture<'_>>, session: &Session<'_, InputCapture<'_>>,
active_clients: &[(CaptureHandle, Position)], active_clients: &[Position],
next_barrier_id: &mut u32, next_barrier_id: &mut u32,
) -> Result<(Vec<ICBarrier>, HashMap<BarrierID, CaptureHandle>), ashpd::Error> { ) -> Result<(Vec<ICBarrier>, HashMap<BarrierID, Position>), ashpd::Error> {
let zones = input_capture.zones(session).await?.response()?; let zones = input_capture.zones(session).await?.response()?;
log::debug!("zones: {zones:?}"); log::debug!("zones: {zones:?}");
@@ -203,9 +203,9 @@ async fn connect_to_eis(
async fn libei_event_handler( async fn libei_event_handler(
mut ei_event_stream: EiConvertEventStream, mut ei_event_stream: EiConvertEventStream,
context: ei::Context, context: ei::Context,
event_tx: Sender<(CaptureHandle, CaptureEvent)>, event_tx: Sender<(Position, CaptureEvent)>,
release_session: Arc<Notify>, release_session: Arc<Notify>,
current_client: Rc<Cell<Option<CaptureHandle>>>, current_pos: Rc<Cell<Option<Position>>>,
) -> Result<(), CaptureError> { ) -> Result<(), CaptureError> {
loop { loop {
let ei_event = ei_event_stream let ei_event = ei_event_stream
@@ -214,7 +214,7 @@ async fn libei_event_handler(
.ok_or(CaptureError::EndOfStream)? .ok_or(CaptureError::EndOfStream)?
.map_err(ReisConvertEventStreamError::from)?; .map_err(ReisConvertEventStreamError::from)?;
log::trace!("from ei: {ei_event:?}"); log::trace!("from ei: {ei_event:?}");
let client = current_client.get(); let client = current_pos.get();
handle_ei_event(ei_event, client, &context, &event_tx, &release_session).await?; handle_ei_event(ei_event, client, &context, &event_tx, &release_session).await?;
} }
} }
@@ -260,14 +260,14 @@ async fn do_capture(
mut capture_event: Receiver<LibeiNotifyEvent>, mut capture_event: Receiver<LibeiNotifyEvent>,
notify_release: Arc<Notify>, notify_release: Arc<Notify>,
session: Option<(Session<'_, InputCapture<'_>>, BitFlags<Capabilities>)>, session: Option<(Session<'_, InputCapture<'_>>, BitFlags<Capabilities>)>,
event_tx: Sender<(CaptureHandle, CaptureEvent)>, event_tx: Sender<(Position, CaptureEvent)>,
cancellation_token: CancellationToken, cancellation_token: CancellationToken,
) -> Result<(), CaptureError> { ) -> Result<(), CaptureError> {
let mut session = session.map(|s| s.0); let mut session = session.map(|s| s.0);
/* safety: libei_task does not outlive Self */ /* safety: libei_task does not outlive Self */
let input_capture = unsafe { &*input_capture }; let input_capture = unsafe { &*input_capture };
let mut active_clients: Vec<(CaptureHandle, Position)> = vec![]; let mut active_clients: Vec<Position> = vec![];
let mut next_barrier_id = 1u32; let mut next_barrier_id = 1u32;
let mut zones_changed = input_capture.receive_zones_changed().await?; let mut zones_changed = input_capture.receive_zones_changed().await?;
@@ -341,8 +341,8 @@ async fn do_capture(
// update clients if requested // update clients if requested
if let Some(event) = capture_event_occured.take() { if let Some(event) = capture_event_occured.take() {
match event { match event {
LibeiNotifyEvent::Create(c, p) => active_clients.push((c, p)), LibeiNotifyEvent::Create(p) => active_clients.push(p),
LibeiNotifyEvent::Destroy(c) => active_clients.retain(|(h, _)| *h != c), LibeiNotifyEvent::Destroy(p) => active_clients.retain(|&pos| pos != p),
} }
} }
@@ -356,21 +356,21 @@ async fn do_capture(
async fn do_capture_session( async fn do_capture_session(
input_capture: &InputCapture<'_>, input_capture: &InputCapture<'_>,
session: &mut Session<'_, InputCapture<'_>>, session: &mut Session<'_, InputCapture<'_>>,
event_tx: &Sender<(CaptureHandle, CaptureEvent)>, event_tx: &Sender<(Position, CaptureEvent)>,
active_clients: &[(CaptureHandle, Position)], active_clients: &[Position],
next_barrier_id: &mut u32, next_barrier_id: &mut u32,
notify_release: &Notify, notify_release: &Notify,
cancel: (CancellationToken, CancellationToken), cancel: (CancellationToken, CancellationToken),
) -> Result<(), CaptureError> { ) -> Result<(), CaptureError> {
let (cancel_session, cancel_update) = cancel; let (cancel_session, cancel_update) = cancel;
// current client // current client
let current_client = Rc::new(Cell::new(None)); let current_pos = Rc::new(Cell::new(None));
// connect to eis server // connect to eis server
let (context, ei_event_stream) = connect_to_eis(input_capture, session).await?; let (context, ei_event_stream) = connect_to_eis(input_capture, session).await?;
// set barriers // set barriers
let (barriers, client_for_barrier_id) = let (barriers, pos_for_barrier_id) =
update_barriers(input_capture, session, active_clients, next_barrier_id).await?; update_barriers(input_capture, session, active_clients, next_barrier_id).await?;
log::debug!("enabling session"); log::debug!("enabling session");
@@ -382,7 +382,7 @@ async fn do_capture_session(
// async event task // async event task
let cancel_ei_handler = CancellationToken::new(); let cancel_ei_handler = CancellationToken::new();
let event_chan = event_tx.clone(); let event_chan = event_tx.clone();
let client = current_client.clone(); let pos = current_pos.clone();
let cancel_session_clone = cancel_session.clone(); let cancel_session_clone = cancel_session.clone();
let release_session_clone = release_session.clone(); let release_session_clone = release_session.clone();
let cancel_ei_handler_clone = cancel_ei_handler.clone(); let cancel_ei_handler_clone = cancel_ei_handler.clone();
@@ -393,7 +393,7 @@ async fn do_capture_session(
context, context,
event_chan, event_chan,
release_session_clone, release_session_clone,
client, pos,
) => { ) => {
log::debug!("libei exited: {r:?} cancelling session task"); log::debug!("libei exited: {r:?} cancelling session task");
cancel_session_clone.cancel(); cancel_session_clone.cancel();
@@ -421,11 +421,11 @@ async fn do_capture_session(
}; };
// find client corresponding to barrier // find client corresponding to barrier
let client = *client_for_barrier_id.get(&barrier_id).expect("invalid barrier id"); let pos = *pos_for_barrier_id.get(&barrier_id).expect("invalid barrier id");
current_client.replace(Some(client)); current_pos.replace(Some(pos));
// client entered => send event // client entered => send event
event_tx.send((client, CaptureEvent::Begin)).await.expect("no channel"); event_tx.send((pos, CaptureEvent::Begin)).await.expect("no channel");
tokio::select! { tokio::select! {
_ = notify_release.notified() => { /* capture release */ _ = notify_release.notified() => { /* capture release */
@@ -441,7 +441,7 @@ async fn do_capture_session(
}, },
} }
release_capture(input_capture, session, activated, client, active_clients).await?; release_capture(input_capture, session, activated, pos).await?;
} }
_ = notify_release.notified() => { /* capture release -> we are not capturing anyway, so ignore */ _ = notify_release.notified() => { /* capture release -> we are not capturing anyway, so ignore */
@@ -484,8 +484,7 @@ async fn release_capture<'a>(
input_capture: &InputCapture<'a>, input_capture: &InputCapture<'a>,
session: &Session<'a, InputCapture<'a>>, session: &Session<'a, InputCapture<'a>>,
activated: Activated, activated: Activated,
current_client: CaptureHandle, current_pos: Position,
active_clients: &[(CaptureHandle, Position)],
) -> Result<(), CaptureError> { ) -> Result<(), CaptureError> {
if let Some(activation_id) = activated.activation_id() { if let Some(activation_id) = activated.activation_id() {
log::debug!("releasing input capture {activation_id}"); log::debug!("releasing input capture {activation_id}");
@@ -494,13 +493,7 @@ async fn release_capture<'a>(
.cursor_position() .cursor_position()
.expect("compositor did not report cursor position!"); .expect("compositor did not report cursor position!");
log::debug!("client entered @ ({x}, {y})"); log::debug!("client entered @ ({x}, {y})");
let pos = active_clients let (dx, dy) = match current_pos {
.iter()
.filter(|(c, _)| *c == current_client)
.map(|(_, p)| p)
.next()
.unwrap(); // FIXME
let (dx, dy) = match pos {
// offset cursor position to not enter again immediately // offset cursor position to not enter again immediately
Position::Left => (1., 0.), Position::Left => (1., 0.),
Position::Right => (-1., 0.), Position::Right => (-1., 0.),
@@ -554,9 +547,9 @@ static ALL_CAPABILITIES: &[DeviceCapability] = &[
async fn handle_ei_event( async fn handle_ei_event(
ei_event: EiEvent, ei_event: EiEvent,
current_client: Option<CaptureHandle>, current_client: Option<Position>,
context: &ei::Context, context: &ei::Context,
event_tx: &Sender<(CaptureHandle, CaptureEvent)>, event_tx: &Sender<(Position, CaptureEvent)>,
release_session: &Notify, release_session: &Notify,
) -> Result<(), CaptureError> { ) -> Result<(), CaptureError> {
match ei_event { match ei_event {
@@ -575,9 +568,9 @@ async fn handle_ei_event(
return Err(CaptureError::Disconnected(format!("{:?}", d.reason))) return Err(CaptureError::Disconnected(format!("{:?}", d.reason)))
} }
_ => { _ => {
if let Some(handle) = current_client { if let Some(pos) = current_client {
for event in Event::from_ei_event(ei_event) { for event in Event::from_ei_event(ei_event) {
event_tx.send((handle, CaptureEvent::Input(event))).await.expect("no channel"); event_tx.send((pos, CaptureEvent::Input(event))).await.expect("no channel");
} }
} }
} }
@@ -587,18 +580,18 @@ async fn handle_ei_event(
#[async_trait] #[async_trait]
impl<'a> LanMouseInputCapture for LibeiInputCapture<'a> { impl<'a> LanMouseInputCapture for LibeiInputCapture<'a> {
async fn create(&mut self, handle: CaptureHandle, pos: Position) -> Result<(), CaptureError> { async fn create(&mut self, pos: Position) -> Result<(), CaptureError> {
let _ = self let _ = self
.notify_capture .notify_capture
.send(LibeiNotifyEvent::Create(handle, pos)) .send(LibeiNotifyEvent::Create(pos))
.await; .await;
Ok(()) Ok(())
} }
async fn destroy(&mut self, handle: CaptureHandle) -> Result<(), CaptureError> { async fn destroy(&mut self, pos: Position) -> Result<(), CaptureError> {
let _ = self let _ = self
.notify_capture .notify_capture
.send(LibeiNotifyEvent::Destroy(handle)) .send(LibeiNotifyEvent::Destroy(pos))
.await; .await;
Ok(()) Ok(())
} }
@@ -629,7 +622,7 @@ impl<'a> Drop for LibeiInputCapture<'a> {
} }
impl<'a> Stream for LibeiInputCapture<'a> { impl<'a> Stream for LibeiInputCapture<'a> {
type Item = Result<(CaptureHandle, CaptureEvent), CaptureError>; type Item = Result<(Position, CaptureEvent), CaptureError>;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> { fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
match self.capture_task.poll_unpin(cx) { match self.capture_task.poll_unpin(cx) {

View File

@@ -1,6 +1,4 @@
use super::{ use super::{error::MacosCaptureCreationError, Capture, CaptureError, CaptureEvent, Position};
error::MacosCaptureCreationError, Capture, CaptureError, CaptureEvent, CaptureHandle, Position,
};
use async_trait::async_trait; use async_trait::async_trait;
use bitflags::bitflags; use bitflags::bitflags;
use core_foundation::base::{kCFAllocatorDefault, CFRelease}; use core_foundation::base::{kCFAllocatorDefault, CFRelease};
@@ -20,14 +18,14 @@ use input_event::{Event, KeyboardEvent, PointerEvent, BTN_LEFT, BTN_MIDDLE, BTN_
use keycode::{KeyMap, KeyMapping}; use keycode::{KeyMap, KeyMapping};
use libc::c_void; use libc::c_void;
use once_cell::unsync::Lazy; use once_cell::unsync::Lazy;
use std::collections::HashMap; use std::collections::HashSet;
use std::ffi::{c_char, CString}; use std::ffi::{c_char, CString};
use std::pin::Pin; use std::pin::Pin;
use std::sync::Arc; use std::sync::Arc;
use std::task::{ready, Context, Poll}; use std::task::{ready, Context, Poll};
use std::thread::{self}; use std::thread::{self};
use tokio::sync::mpsc::{Receiver, Sender}; use tokio::sync::mpsc::{self, Receiver, Sender};
use tokio::sync::Mutex; use tokio::sync::{oneshot, Mutex};
#[derive(Debug, Default)] #[derive(Debug, Default)]
struct Bounds { struct Bounds {
@@ -39,44 +37,44 @@ struct Bounds {
#[derive(Debug)] #[derive(Debug)]
struct InputCaptureState { struct InputCaptureState {
client_for_pos: Lazy<HashMap<Position, CaptureHandle>>, active_clients: Lazy<HashSet<Position>>,
current_client: Option<(CaptureHandle, Position)>, current_pos: Option<Position>,
bounds: Bounds, bounds: Bounds,
} }
#[derive(Debug)] #[derive(Debug)]
enum ProducerEvent { enum ProducerEvent {
Release, Release,
Create(CaptureHandle, Position), Create(Position),
Destroy(CaptureHandle), Destroy(Position),
Grab((CaptureHandle, Position)), Grab(Position),
EventTapDisabled, EventTapDisabled,
} }
impl InputCaptureState { impl InputCaptureState {
fn new() -> Result<Self, MacosCaptureCreationError> { fn new() -> Result<Self, MacosCaptureCreationError> {
let mut res = Self { let mut res = Self {
client_for_pos: Lazy::new(HashMap::new), active_clients: Lazy::new(HashSet::new),
current_client: None, current_pos: None,
bounds: Bounds::default(), bounds: Bounds::default(),
}; };
res.update_bounds()?; res.update_bounds()?;
Ok(res) Ok(res)
} }
fn crossed(&mut self, event: &CGEvent) -> Option<(CaptureHandle, Position)> { fn crossed(&mut self, event: &CGEvent) -> Option<Position> {
let location = event.location(); let location = event.location();
let relative_x = event.get_double_value_field(EventField::MOUSE_EVENT_DELTA_X); let relative_x = event.get_double_value_field(EventField::MOUSE_EVENT_DELTA_X);
let relative_y = event.get_double_value_field(EventField::MOUSE_EVENT_DELTA_Y); let relative_y = event.get_double_value_field(EventField::MOUSE_EVENT_DELTA_Y);
for (position, client) in self.client_for_pos.iter() { for &position in self.active_clients.iter() {
if (position == &Position::Left && (location.x + relative_x) <= self.bounds.xmin) if (position == Position::Left && (location.x + relative_x) <= self.bounds.xmin)
|| (position == &Position::Right && (location.x + relative_x) >= self.bounds.xmax) || (position == Position::Right && (location.x + relative_x) >= self.bounds.xmax)
|| (position == &Position::Top && (location.y + relative_y) <= self.bounds.ymin) || (position == Position::Top && (location.y + relative_y) <= self.bounds.ymin)
|| (position == &Position::Bottom && (location.y + relative_y) >= self.bounds.ymax) || (position == Position::Bottom && (location.y + relative_y) >= self.bounds.ymax)
{ {
log::debug!("Crossed barrier into client: {client}, {position:?}"); log::debug!("Crossed barrier into position: {position:?}");
return Some((*client, *position)); return Some(position);
} }
} }
None None
@@ -102,7 +100,7 @@ impl InputCaptureState {
// to the edge of the screen, the cursor will be hidden but we dont want it to appear in a // to the edge of the screen, the cursor will be hidden but we dont want it to appear in a
// random location when we exit the client // random location when we exit the client
fn reset_mouse_position(&self, event: &CGEvent) -> Result<(), CaptureError> { fn reset_mouse_position(&self, event: &CGEvent) -> Result<(), CaptureError> {
if let Some((_, pos)) = self.current_client { if let Some(pos) = self.current_pos {
let location = event.location(); let location = event.location();
let edge_offset = 1.0; let edge_offset = 1.0;
@@ -146,40 +144,31 @@ impl InputCaptureState {
log::debug!("handling event: {producer_event:?}"); log::debug!("handling event: {producer_event:?}");
match producer_event { match producer_event {
ProducerEvent::Release => { ProducerEvent::Release => {
if self.current_client.is_some() { if self.current_pos.is_some() {
CGDisplay::show_cursor(&CGDisplay::main()) CGDisplay::show_cursor(&CGDisplay::main())
.map_err(CaptureError::CoreGraphics)?; .map_err(CaptureError::CoreGraphics)?;
self.current_client = None; self.current_pos = None;
} }
} }
ProducerEvent::Grab(client) => { ProducerEvent::Grab(pos) => {
if self.current_client.is_none() { if self.current_pos.is_none() {
CGDisplay::hide_cursor(&CGDisplay::main()) CGDisplay::hide_cursor(&CGDisplay::main())
.map_err(CaptureError::CoreGraphics)?; .map_err(CaptureError::CoreGraphics)?;
self.current_client = Some(client); self.current_pos = Some(pos);
} }
} }
ProducerEvent::Create(c, p) => { ProducerEvent::Create(p) => {
self.client_for_pos.insert(p, c); self.active_clients.insert(p);
} }
ProducerEvent::Destroy(c) => { ProducerEvent::Destroy(p) => {
for pos in [ if let Some(current) = self.current_pos {
Position::Left, if current == p {
Position::Right, CGDisplay::show_cursor(&CGDisplay::main())
Position::Top, .map_err(CaptureError::CoreGraphics)?;
Position::Bottom, self.current_pos = None;
] { };
if let Some((current_c, _)) = self.current_client {
if current_c == c {
CGDisplay::show_cursor(&CGDisplay::main())
.map_err(CaptureError::CoreGraphics)?;
self.current_client = None;
};
}
if self.client_for_pos.get(&pos).copied() == Some(c) {
self.client_for_pos.remove(&pos);
}
} }
self.active_clients.remove(&p);
} }
ProducerEvent::EventTapDisabled => return Err(CaptureError::EventTapDisabled), ProducerEvent::EventTapDisabled => return Err(CaptureError::EventTapDisabled),
}; };
@@ -333,12 +322,11 @@ fn get_events(
Ok(()) Ok(())
} }
fn event_tap_thread( fn create_event_tap<'a>(
client_state: Arc<Mutex<InputCaptureState>>, client_state: Arc<Mutex<InputCaptureState>>,
event_tx: Sender<(CaptureHandle, CaptureEvent)>,
notify_tx: Sender<ProducerEvent>, notify_tx: Sender<ProducerEvent>,
exit: tokio::sync::oneshot::Sender<Result<(), &'static str>>, event_tx: Sender<(Position, CaptureEvent)>,
) { ) -> Result<CGEventTap<'a>, MacosCaptureCreationError> {
let cg_events_of_interest: Vec<CGEventType> = vec![ let cg_events_of_interest: Vec<CGEventType> = vec![
CGEventType::LeftMouseDown, CGEventType::LeftMouseDown,
CGEventType::LeftMouseUp, CGEventType::LeftMouseUp,
@@ -356,15 +344,11 @@ fn event_tap_thread(
CGEventType::FlagsChanged, CGEventType::FlagsChanged,
]; ];
let tap = CGEventTap::new( let event_tap_callback =
CGEventTapLocation::Session, move |_proxy: CGEventTapProxy, event_type: CGEventType, cg_ev: &CGEvent| {
CGEventTapPlacement::HeadInsertEventTap,
CGEventTapOptions::Default,
cg_events_of_interest,
|_proxy: CGEventTapProxy, event_type: CGEventType, cg_ev: &CGEvent| {
log::trace!("Got event from tap: {event_type:?}"); log::trace!("Got event from tap: {event_type:?}");
let mut state = client_state.blocking_lock(); let mut state = client_state.blocking_lock();
let mut client = None; let mut pos = None;
let mut res_events = vec![]; let mut res_events = vec![];
if matches!( if matches!(
@@ -380,8 +364,8 @@ fn event_tap_thread(
} }
// Are we in a client? // Are we in a client?
if let Some((current_client, _)) = state.current_client { if let Some(current_pos) = state.current_pos {
client = Some(current_client); pos = Some(current_pos);
get_events(&event_type, cg_ev, &mut res_events).unwrap_or_else(|e| { get_events(&event_type, cg_ev, &mut res_events).unwrap_or_else(|e| {
log::error!("Failed to get events: {e}"); log::error!("Failed to get events: {e}");
}); });
@@ -395,19 +379,19 @@ fn event_tap_thread(
} }
// Did we cross a barrier? // Did we cross a barrier?
else if matches!(event_type, CGEventType::MouseMoved) { else if matches!(event_type, CGEventType::MouseMoved) {
if let Some((new_client, pos)) = state.crossed(cg_ev) { if let Some(new_pos) = state.crossed(cg_ev) {
client = Some(new_client); pos = Some(new_pos);
res_events.push(CaptureEvent::Begin); res_events.push(CaptureEvent::Begin);
notify_tx notify_tx
.blocking_send(ProducerEvent::Grab((new_client, pos))) .blocking_send(ProducerEvent::Grab(new_pos))
.expect("Failed to send notification"); .expect("Failed to send notification");
} }
} }
if let Some(client) = client { if let Some(pos) = pos {
res_events.iter().for_each(|e| { res_events.iter().for_each(|e| {
event_tx event_tx
.blocking_send((client, *e)) .blocking_send((pos, *e))
.expect("Failed to send event"); .expect("Failed to send event");
}); });
// Returning None should stop the event from being processed // Returning None should stop the event from being processed
@@ -415,9 +399,16 @@ fn event_tap_thread(
cg_ev.set_type(CGEventType::Null); cg_ev.set_type(CGEventType::Null);
} }
Some(cg_ev.to_owned()) Some(cg_ev.to_owned())
}, };
let tap = CGEventTap::new(
CGEventTapLocation::Session,
CGEventTapPlacement::HeadInsertEventTap,
CGEventTapOptions::Default,
cg_events_of_interest,
event_tap_callback,
) )
.expect("Failed creating tap"); .map_err(|_| MacosCaptureCreationError::EventTapCreation)?;
let tap_source: CFRunLoopSource = tap let tap_source: CFRunLoopSource = tap
.mach_port .mach_port
@@ -428,22 +419,43 @@ fn event_tap_thread(
CFRunLoop::get_current().add_source(&tap_source, kCFRunLoopCommonModes); CFRunLoop::get_current().add_source(&tap_source, kCFRunLoopCommonModes);
} }
Ok(tap)
}
fn event_tap_thread(
client_state: Arc<Mutex<InputCaptureState>>,
event_tx: Sender<(Position, CaptureEvent)>,
notify_tx: Sender<ProducerEvent>,
ready: std::sync::mpsc::Sender<Result<(), MacosCaptureCreationError>>,
exit: oneshot::Sender<Result<(), &'static str>>,
) {
let _tap = match create_event_tap(client_state, notify_tx, event_tx) {
Err(e) => {
ready.send(Err(e)).expect("channel closed");
return;
}
Ok(tap) => {
ready.send(Ok(())).expect("channel closed");
tap
}
};
CFRunLoop::run_current(); CFRunLoop::run_current();
let _ = exit.send(Err("tap thread exited")); let _ = exit.send(Err("tap thread exited"));
} }
pub struct MacOSInputCapture { pub struct MacOSInputCapture {
event_rx: Receiver<(CaptureHandle, CaptureEvent)>, event_rx: Receiver<(Position, CaptureEvent)>,
notify_tx: Sender<ProducerEvent>, notify_tx: Sender<ProducerEvent>,
} }
impl MacOSInputCapture { impl MacOSInputCapture {
pub async fn new() -> Result<Self, MacosCaptureCreationError> { pub async fn new() -> Result<Self, MacosCaptureCreationError> {
let state = Arc::new(Mutex::new(InputCaptureState::new()?)); let state = Arc::new(Mutex::new(InputCaptureState::new()?));
let (event_tx, event_rx) = tokio::sync::mpsc::channel(32); let (event_tx, event_rx) = mpsc::channel(32);
let (notify_tx, mut notify_rx) = tokio::sync::mpsc::channel(32); let (notify_tx, mut notify_rx) = mpsc::channel(32);
let (tap_exit_tx, mut tap_exit_rx) = tokio::sync::oneshot::channel(); let (ready_tx, ready_rx) = std::sync::mpsc::channel();
let (tap_exit_tx, mut tap_exit_rx) = oneshot::channel();
unsafe { unsafe {
configure_cf_settings()?; configure_cf_settings()?;
@@ -457,10 +469,14 @@ impl MacOSInputCapture {
event_tap_thread_state, event_tap_thread_state,
event_tx, event_tx,
event_tap_notify, event_tap_notify,
ready_tx,
tap_exit_tx, tap_exit_tx,
) )
}); });
// wait for event tap creation result
ready_rx.recv().expect("channel closed")?;
let _tap_task: tokio::task::JoinHandle<()> = tokio::task::spawn_local(async move { let _tap_task: tokio::task::JoinHandle<()> = tokio::task::spawn_local(async move {
loop { loop {
tokio::select! { tokio::select! {
@@ -491,21 +507,21 @@ impl MacOSInputCapture {
#[async_trait] #[async_trait]
impl Capture for MacOSInputCapture { impl Capture for MacOSInputCapture {
async fn create(&mut self, id: CaptureHandle, pos: Position) -> Result<(), CaptureError> { async fn create(&mut self, pos: Position) -> Result<(), CaptureError> {
let notify_tx = self.notify_tx.clone(); let notify_tx = self.notify_tx.clone();
tokio::task::spawn_local(async move { tokio::task::spawn_local(async move {
log::debug!("creating client {id}, {pos}"); log::debug!("creating capture, {pos}");
let _ = notify_tx.send(ProducerEvent::Create(id, pos)).await; let _ = notify_tx.send(ProducerEvent::Create(pos)).await;
log::debug!("done !"); log::debug!("done !");
}); });
Ok(()) Ok(())
} }
async fn destroy(&mut self, id: CaptureHandle) -> Result<(), CaptureError> { async fn destroy(&mut self, pos: Position) -> Result<(), CaptureError> {
let notify_tx = self.notify_tx.clone(); let notify_tx = self.notify_tx.clone();
tokio::task::spawn_local(async move { tokio::task::spawn_local(async move {
log::debug!("destroying client {id}"); log::debug!("destroying capture {pos}");
let _ = notify_tx.send(ProducerEvent::Destroy(id)).await; let _ = notify_tx.send(ProducerEvent::Destroy(pos)).await;
log::debug!("done !"); log::debug!("done !");
}); });
Ok(()) Ok(())
@@ -526,7 +542,7 @@ impl Capture for MacOSInputCapture {
} }
impl Stream for MacOSInputCapture { impl Stream for MacOSInputCapture {
type Item = Result<(CaptureHandle, CaptureEvent), CaptureError>; type Item = Result<(Position, CaptureEvent), CaptureError>;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> { fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
match ready!(self.event_rx.poll_recv(cx)) { match ready!(self.event_rx.poll_recv(cx)) {

View File

@@ -64,7 +64,7 @@ use crate::{CaptureError, CaptureEvent};
use super::{ use super::{
error::{LayerShellCaptureCreationError, WaylandBindError}, error::{LayerShellCaptureCreationError, WaylandBindError},
Capture, CaptureHandle, Position, Capture, Position,
}; };
struct Globals { struct Globals {
@@ -102,13 +102,13 @@ struct State {
pointer_lock: Option<ZwpLockedPointerV1>, pointer_lock: Option<ZwpLockedPointerV1>,
rel_pointer: Option<ZwpRelativePointerV1>, rel_pointer: Option<ZwpRelativePointerV1>,
shortcut_inhibitor: Option<ZwpKeyboardShortcutsInhibitorV1>, shortcut_inhibitor: Option<ZwpKeyboardShortcutsInhibitorV1>,
client_for_window: Vec<(Arc<Window>, CaptureHandle)>, active_windows: Vec<Arc<Window>>,
focused: Option<(Arc<Window>, CaptureHandle)>, focused: Option<Arc<Window>>,
g: Globals, g: Globals,
wayland_fd: RawFd, wayland_fd: RawFd,
read_guard: Option<ReadEventsGuard>, read_guard: Option<ReadEventsGuard>,
qh: QueueHandle<Self>, qh: QueueHandle<Self>,
pending_events: VecDeque<(CaptureHandle, CaptureEvent)>, pending_events: VecDeque<(Position, CaptureEvent)>,
output_info: Vec<(WlOutput, OutputInfo)>, output_info: Vec<(WlOutput, OutputInfo)>,
scroll_discrete_pending: bool, scroll_discrete_pending: bool,
} }
@@ -124,7 +124,7 @@ impl AsRawFd for Inner {
} }
} }
pub struct WaylandInputCapture(AsyncFd<Inner>); pub struct LayerShellInputCapture(AsyncFd<Inner>);
struct Window { struct Window {
buffer: wl_buffer::WlBuffer, buffer: wl_buffer::WlBuffer,
@@ -256,7 +256,7 @@ fn draw(f: &mut File, (width, height): (u32, u32)) {
} }
} }
impl WaylandInputCapture { impl LayerShellInputCapture {
pub fn new() -> std::result::Result<Self, LayerShellCaptureCreationError> { pub fn new() -> std::result::Result<Self, LayerShellCaptureCreationError> {
let conn = Connection::connect_to_env()?; let conn = Connection::connect_to_env()?;
let (g, mut queue) = registry_queue_init::<State>(&conn)?; let (g, mut queue) = registry_queue_init::<State>(&conn)?;
@@ -323,7 +323,7 @@ impl WaylandInputCapture {
pointer_lock: None, pointer_lock: None,
rel_pointer: None, rel_pointer: None,
shortcut_inhibitor: None, shortcut_inhibitor: None,
client_for_window: Vec::new(), active_windows: Vec::new(),
focused: None, focused: None,
qh, qh,
wayland_fd, wayland_fd,
@@ -370,23 +370,18 @@ impl WaylandInputCapture {
let inner = AsyncFd::new(Inner { queue, state })?; let inner = AsyncFd::new(Inner { queue, state })?;
Ok(WaylandInputCapture(inner)) Ok(LayerShellInputCapture(inner))
} }
fn add_client(&mut self, handle: CaptureHandle, pos: Position) { fn add_client(&mut self, pos: Position) {
self.0.get_mut().state.add_client(handle, pos); self.0.get_mut().state.add_client(pos);
} }
fn delete_client(&mut self, handle: CaptureHandle) { fn delete_client(&mut self, pos: Position) {
let inner = self.0.get_mut(); let inner = self.0.get_mut();
// remove all windows corresponding to this client // remove all windows corresponding to this client
while let Some(i) = inner while let Some(i) = inner.state.active_windows.iter().position(|w| w.pos == pos) {
.state inner.state.active_windows.remove(i);
.client_for_window
.iter()
.position(|(_, c)| *c == handle)
{
inner.state.client_for_window.remove(i);
inner.state.focused = None; inner.state.focused = None;
} }
} }
@@ -400,7 +395,7 @@ impl State {
serial: u32, serial: u32,
qh: &QueueHandle<State>, qh: &QueueHandle<State>,
) { ) {
let (window, _) = self.focused.as_ref().unwrap(); let window = self.focused.as_ref().unwrap();
// hide the cursor // hide the cursor
pointer.set_cursor(serial, None, 0, 0); pointer.set_cursor(serial, None, 0, 0);
@@ -443,7 +438,7 @@ impl State {
fn ungrab(&mut self) { fn ungrab(&mut self) {
// get focused client // get focused client
let (window, _client) = match self.focused.as_ref() { let window = match self.focused.as_ref() {
Some(focused) => focused, Some(focused) => focused,
None => return, None => return,
}; };
@@ -473,27 +468,23 @@ impl State {
} }
} }
fn add_client(&mut self, client: CaptureHandle, pos: Position) { fn add_client(&mut self, pos: Position) {
let outputs = get_output_configuration(self, pos); let outputs = get_output_configuration(self, pos);
log::debug!("outputs: {outputs:?}"); log::debug!("outputs: {outputs:?}");
outputs.iter().for_each(|(o, i)| { outputs.iter().for_each(|(o, i)| {
let window = Window::new(self, &self.qh, o, pos, i.size); let window = Window::new(self, &self.qh, o, pos, i.size);
let window = Arc::new(window); let window = Arc::new(window);
self.client_for_window.push((window, client)); self.active_windows.push(window);
}); });
} }
fn update_windows(&mut self) { fn update_windows(&mut self) {
log::debug!("updating windows"); log::debug!("updating windows");
log::debug!("output info: {:?}", self.output_info); log::debug!("output info: {:?}", self.output_info);
let clients: Vec<_> = self let clients: Vec<_> = self.active_windows.drain(..).map(|w| w.pos).collect();
.client_for_window for pos in clients {
.drain(..) self.add_client(pos);
.map(|(w, c)| (c, w.pos))
.collect();
for (client, pos) in clients {
self.add_client(client, pos);
} }
} }
} }
@@ -566,15 +557,15 @@ impl Inner {
} }
#[async_trait] #[async_trait]
impl Capture for WaylandInputCapture { impl Capture for LayerShellInputCapture {
async fn create(&mut self, handle: CaptureHandle, pos: Position) -> Result<(), CaptureError> { async fn create(&mut self, pos: Position) -> Result<(), CaptureError> {
self.add_client(handle, pos); self.add_client(pos);
let inner = self.0.get_mut(); let inner = self.0.get_mut();
Ok(inner.flush_events()?) Ok(inner.flush_events()?)
} }
async fn destroy(&mut self, handle: CaptureHandle) -> Result<(), CaptureError> { async fn destroy(&mut self, pos: Position) -> Result<(), CaptureError> {
self.delete_client(handle); self.delete_client(pos);
let inner = self.0.get_mut(); let inner = self.0.get_mut();
Ok(inner.flush_events()?) Ok(inner.flush_events()?)
} }
@@ -591,8 +582,8 @@ impl Capture for WaylandInputCapture {
} }
} }
impl Stream for WaylandInputCapture { impl Stream for LayerShellInputCapture {
type Item = Result<(CaptureHandle, CaptureEvent), CaptureError>; type Item = Result<(Position, CaptureEvent), CaptureError>;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> { fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
if let Some(event) = self.0.get_mut().state.pending_events.pop_front() { if let Some(event) = self.0.get_mut().state.pending_events.pop_front() {
@@ -685,23 +676,20 @@ impl Dispatch<WlPointer, ()> for State {
} => { } => {
// get client corresponding to the focused surface // get client corresponding to the focused surface
{ {
if let Some((window, client)) = app if let Some(window) = app.active_windows.iter().find(|w| w.surface == surface) {
.client_for_window app.focused = Some(window.clone());
.iter()
.find(|(w, _c)| w.surface == surface)
{
app.focused = Some((window.clone(), *client));
app.grab(&surface, pointer, serial, qh); app.grab(&surface, pointer, serial, qh);
} else { } else {
return; return;
} }
} }
let (_, client) = app let pos = app
.client_for_window .active_windows
.iter() .iter()
.find(|(w, _c)| w.surface == surface) .find(|w| w.surface == surface)
.map(|w| w.pos)
.unwrap(); .unwrap();
app.pending_events.push_back((*client, CaptureEvent::Begin)); app.pending_events.push_back((pos, CaptureEvent::Begin));
} }
wl_pointer::Event::Leave { .. } => { wl_pointer::Event::Leave { .. } => {
/* There are rare cases, where when a window is opened in /* There are rare cases, where when a window is opened in
@@ -722,9 +710,9 @@ impl Dispatch<WlPointer, ()> for State {
button, button,
state, state,
} => { } => {
let (_, client) = app.focused.as_ref().unwrap(); let window = app.focused.as_ref().unwrap();
app.pending_events.push_back(( app.pending_events.push_back((
*client, window.pos,
CaptureEvent::Input(Event::Pointer(PointerEvent::Button { CaptureEvent::Input(Event::Pointer(PointerEvent::Button {
time, time,
button, button,
@@ -733,7 +721,7 @@ impl Dispatch<WlPointer, ()> for State {
)); ));
} }
wl_pointer::Event::Axis { time, axis, value } => { wl_pointer::Event::Axis { time, axis, value } => {
let (_, client) = app.focused.as_ref().unwrap(); let window = app.focused.as_ref().unwrap();
if app.scroll_discrete_pending { if app.scroll_discrete_pending {
// each axisvalue120 event is coupled with // each axisvalue120 event is coupled with
// a corresponding axis event, which needs to // a corresponding axis event, which needs to
@@ -741,7 +729,7 @@ impl Dispatch<WlPointer, ()> for State {
app.scroll_discrete_pending = false; app.scroll_discrete_pending = false;
} else { } else {
app.pending_events.push_back(( app.pending_events.push_back((
*client, window.pos,
CaptureEvent::Input(Event::Pointer(PointerEvent::Axis { CaptureEvent::Input(Event::Pointer(PointerEvent::Axis {
time, time,
axis: u32::from(axis) as u8, axis: u32::from(axis) as u8,
@@ -751,10 +739,10 @@ impl Dispatch<WlPointer, ()> for State {
} }
} }
wl_pointer::Event::AxisValue120 { axis, value120 } => { wl_pointer::Event::AxisValue120 { axis, value120 } => {
let (_, client) = app.focused.as_ref().unwrap(); let window = app.focused.as_ref().unwrap();
app.scroll_discrete_pending = true; app.scroll_discrete_pending = true;
app.pending_events.push_back(( app.pending_events.push_back((
*client, window.pos,
CaptureEvent::Input(Event::Pointer(PointerEvent::AxisDiscrete120 { CaptureEvent::Input(Event::Pointer(PointerEvent::AxisDiscrete120 {
axis: u32::from(axis) as u8, axis: u32::from(axis) as u8,
value: value120, value: value120,
@@ -780,10 +768,7 @@ impl Dispatch<WlKeyboard, ()> for State {
_: &Connection, _: &Connection,
_: &QueueHandle<Self>, _: &QueueHandle<Self>,
) { ) {
let (_window, client) = match &app.focused { let window = &app.focused;
Some(focused) => (Some(&focused.0), Some(&focused.1)),
None => (None, None),
};
match event { match event {
wl_keyboard::Event::Key { wl_keyboard::Event::Key {
serial: _, serial: _,
@@ -791,9 +776,9 @@ impl Dispatch<WlKeyboard, ()> for State {
key, key,
state, state,
} => { } => {
if let Some(client) = client { if let Some(window) = window {
app.pending_events.push_back(( app.pending_events.push_back((
*client, window.pos,
CaptureEvent::Input(Event::Keyboard(KeyboardEvent::Key { CaptureEvent::Input(Event::Keyboard(KeyboardEvent::Key {
time, time,
key, key,
@@ -809,9 +794,9 @@ impl Dispatch<WlKeyboard, ()> for State {
mods_locked, mods_locked,
group, group,
} => { } => {
if let Some(client) = client { if let Some(window) = window {
app.pending_events.push_back(( app.pending_events.push_back((
*client, window.pos,
CaptureEvent::Input(Event::Keyboard(KeyboardEvent::Modifiers { CaptureEvent::Input(Event::Keyboard(KeyboardEvent::Modifiers {
depressed: mods_depressed, depressed: mods_depressed,
latched: mods_latched, latched: mods_latched,
@@ -843,10 +828,10 @@ impl Dispatch<ZwpRelativePointerV1, ()> for State {
.. ..
} = event } = event
{ {
if let Some((_window, client)) = &app.focused { if let Some(window) = &app.focused {
let time = (((utime_hi as u64) << 32 | utime_lo as u64) / 1000) as u32; let time = (((utime_hi as u64) << 32 | utime_lo as u64) / 1000) as u32;
app.pending_events.push_back(( app.pending_events.push_back((
*client, window.pos,
CaptureEvent::Input(Event::Pointer(PointerEvent::Motion { time, dx, dy })), CaptureEvent::Input(Event::Pointer(PointerEvent::Motion { time, dx, dy })),
)); ));
} }
@@ -864,10 +849,10 @@ impl Dispatch<ZwlrLayerSurfaceV1, ()> for State {
_: &QueueHandle<Self>, _: &QueueHandle<Self>,
) { ) {
if let zwlr_layer_surface_v1::Event::Configure { serial, .. } = event { if let zwlr_layer_surface_v1::Event::Configure { serial, .. } = event {
if let Some((window, _client)) = app if let Some(window) = app
.client_for_window .active_windows
.iter() .iter()
.find(|(w, _c)| &w.layer_surface == layer_surface) .find(|w| &w.layer_surface == layer_surface)
{ {
// client corresponding to the layer_surface // client corresponding to the layer_surface
let surface = &window.surface; let surface = &window.surface;

View File

@@ -3,12 +3,12 @@ use core::task::{Context, Poll};
use futures::Stream; use futures::Stream;
use once_cell::unsync::Lazy; use once_cell::unsync::Lazy;
use std::collections::HashMap; use std::collections::HashSet;
use std::ptr::{addr_of, addr_of_mut}; use std::ptr::{addr_of, addr_of_mut};
use futures::executor::block_on; use futures::executor::block_on;
use std::default::Default; use std::default::Default;
use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use std::sync::{mpsc, Mutex}; use std::sync::{mpsc, Mutex};
use std::task::ready; use std::task::ready;
use std::{pin::Pin, thread}; use std::{pin::Pin, thread};
@@ -37,15 +37,15 @@ use input_event::{
Event, KeyboardEvent, PointerEvent, BTN_BACK, BTN_FORWARD, BTN_LEFT, BTN_MIDDLE, BTN_RIGHT, Event, KeyboardEvent, PointerEvent, BTN_BACK, BTN_FORWARD, BTN_LEFT, BTN_MIDDLE, BTN_RIGHT,
}; };
use super::{Capture, CaptureError, CaptureEvent, CaptureHandle, Position}; use super::{Capture, CaptureError, CaptureEvent, Position};
enum Request { enum Request {
Create(CaptureHandle, Position), Create(Position),
Destroy(CaptureHandle), Destroy(Position),
} }
pub struct WindowsInputCapture { pub struct WindowsInputCapture {
event_rx: Receiver<(CaptureHandle, CaptureEvent)>, event_rx: Receiver<(Position, CaptureEvent)>,
msg_thread: Option<std::thread::JoinHandle<()>>, msg_thread: Option<std::thread::JoinHandle<()>>,
} }
@@ -65,22 +65,22 @@ unsafe fn signal_message_thread(event_type: EventType) {
#[async_trait] #[async_trait]
impl Capture for WindowsInputCapture { impl Capture for WindowsInputCapture {
async fn create(&mut self, handle: CaptureHandle, pos: Position) -> Result<(), CaptureError> { async fn create(&mut self, pos: Position) -> Result<(), CaptureError> {
unsafe { unsafe {
{ {
let mut requests = REQUEST_BUFFER.lock().unwrap(); let mut requests = REQUEST_BUFFER.lock().unwrap();
requests.push(Request::Create(handle, pos)); requests.push(Request::Create(pos));
} }
signal_message_thread(EventType::Request); signal_message_thread(EventType::Request);
} }
Ok(()) Ok(())
} }
async fn destroy(&mut self, handle: CaptureHandle) -> Result<(), CaptureError> { async fn destroy(&mut self, pos: Position) -> Result<(), CaptureError> {
unsafe { unsafe {
{ {
let mut requests = REQUEST_BUFFER.lock().unwrap(); let mut requests = REQUEST_BUFFER.lock().unwrap();
requests.push(Request::Destroy(handle)); requests.push(Request::Destroy(pos));
} }
signal_message_thread(EventType::Request); signal_message_thread(EventType::Request);
} }
@@ -98,9 +98,9 @@ impl Capture for WindowsInputCapture {
} }
static mut REQUEST_BUFFER: Mutex<Vec<Request>> = Mutex::new(Vec::new()); static mut REQUEST_BUFFER: Mutex<Vec<Request>> = Mutex::new(Vec::new());
static mut ACTIVE_CLIENT: Option<CaptureHandle> = None; static mut ACTIVE_CLIENT: Option<Position> = None;
static mut CLIENT_FOR_POS: Lazy<HashMap<Position, CaptureHandle>> = Lazy::new(HashMap::new); static mut CLIENTS: Lazy<HashSet<Position>> = Lazy::new(HashSet::new);
static mut EVENT_TX: Option<Sender<(CaptureHandle, CaptureEvent)>> = None; static mut EVENT_TX: Option<Sender<(Position, CaptureEvent)>> = None;
static mut EVENT_THREAD_ID: AtomicU32 = AtomicU32::new(0); static mut EVENT_THREAD_ID: AtomicU32 = AtomicU32::new(0);
unsafe fn set_event_tid(tid: u32) { unsafe fn set_event_tid(tid: u32) {
EVENT_THREAD_ID.store(tid, Ordering::SeqCst); EVENT_THREAD_ID.store(tid, Ordering::SeqCst);
@@ -281,12 +281,12 @@ unsafe fn check_client_activation(wparam: WPARAM, lparam: LPARAM) -> bool {
}; };
/* check if a client is registered for the barrier */ /* check if a client is registered for the barrier */
let Some(client) = CLIENT_FOR_POS.get(&pos) else { if !CLIENTS.contains(&pos) {
return ret; return ret;
}; }
/* update active client and entry point */ /* update active client and entry point */
ACTIVE_CLIENT.replace(*client); ACTIVE_CLIENT.replace(pos);
ENTRY_POINT = clamp_to_display_bounds(prev_pos, curr_pos); ENTRY_POINT = clamp_to_display_bounds(prev_pos, curr_pos);
/* notify main thread */ /* notify main thread */
@@ -305,7 +305,7 @@ unsafe extern "system" fn mouse_proc(ncode: i32, wparam: WPARAM, lparam: LPARAM)
} }
/* get active client if any */ /* get active client if any */
let Some(client) = ACTIVE_CLIENT else { let Some(pos) = ACTIVE_CLIENT else {
return LRESULT(1); return LRESULT(1);
}; };
@@ -313,7 +313,7 @@ unsafe extern "system" fn mouse_proc(ncode: i32, wparam: WPARAM, lparam: LPARAM)
let Some(pointer_event) = to_mouse_event(wparam, lparam) else { let Some(pointer_event) = to_mouse_event(wparam, lparam) else {
return LRESULT(1); return LRESULT(1);
}; };
let event = (client, CaptureEvent::Input(Event::Pointer(pointer_event))); let event = (pos, CaptureEvent::Input(Event::Pointer(pointer_event)));
/* notify mainthread (drop events if sending too fast) */ /* notify mainthread (drop events if sending too fast) */
if let Err(e) = EVENT_TX.as_ref().unwrap().try_send(event) { if let Err(e) = EVENT_TX.as_ref().unwrap().try_send(event) {
@@ -493,6 +493,8 @@ fn get_msg() -> Option<MSG> {
} }
} }
static WINDOW_CLASS_REGISTERED: AtomicBool = AtomicBool::new(false);
fn message_thread(ready_tx: mpsc::Sender<()>) { fn message_thread(ready_tx: mpsc::Sender<()>) {
unsafe { unsafe {
set_event_tid(GetCurrentThreadId()); set_event_tid(GetCurrentThreadId());
@@ -513,9 +515,15 @@ fn message_thread(ready_tx: mpsc::Sender<()>) {
..Default::default() ..Default::default()
}; };
let ret = RegisterClassW(&window_class); if WINDOW_CLASS_REGISTERED
if ret == 0 { .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
panic!("RegisterClassW"); .is_ok()
{
/* register window class if not yet done so */
let ret = RegisterClassW(&window_class);
if ret == 0 {
panic!("RegisterClassW");
}
} }
/* window is used ro receive WM_DISPLAYCHANGE messages */ /* window is used ro receive WM_DISPLAYCHANGE messages */
@@ -575,23 +583,16 @@ fn message_thread(ready_tx: mpsc::Sender<()>) {
fn update_clients(request: Request) { fn update_clients(request: Request) {
match request { match request {
Request::Create(handle, pos) => { Request::Create(pos) => {
unsafe { CLIENT_FOR_POS.insert(pos, handle) }; unsafe { CLIENTS.insert(pos) };
} }
Request::Destroy(handle) => unsafe { Request::Destroy(pos) => unsafe {
for pos in [ if let Some(active_pos) = ACTIVE_CLIENT {
Position::Left, if pos == active_pos {
Position::Right, let _ = ACTIVE_CLIENT.take();
Position::Top,
Position::Bottom,
] {
if ACTIVE_CLIENT == Some(handle) {
ACTIVE_CLIENT.take();
}
if CLIENT_FOR_POS.get(&pos).copied() == Some(handle) {
CLIENT_FOR_POS.remove(&pos);
} }
} }
CLIENTS.remove(&pos);
}, },
} }
} }
@@ -614,7 +615,7 @@ impl WindowsInputCapture {
} }
impl Stream for WindowsInputCapture { impl Stream for WindowsInputCapture {
type Item = Result<(CaptureHandle, CaptureEvent), CaptureError>; type Item = Result<(Position, CaptureEvent), CaptureError>;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> { fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
match ready!(self.event_rx.poll_recv(cx)) { match ready!(self.event_rx.poll_recv(cx)) {
None => Poll::Ready(None), None => Poll::Ready(None),

View File

@@ -3,10 +3,7 @@ use std::task::Poll;
use async_trait::async_trait; use async_trait::async_trait;
use futures_core::Stream; use futures_core::Stream;
use super::{ use super::{error::X11InputCaptureCreationError, Capture, CaptureError, CaptureEvent, Position};
error::X11InputCaptureCreationError, Capture, CaptureError, CaptureEvent, CaptureHandle,
Position,
};
pub struct X11InputCapture {} pub struct X11InputCapture {}
@@ -18,11 +15,11 @@ impl X11InputCapture {
#[async_trait] #[async_trait]
impl Capture for X11InputCapture { impl Capture for X11InputCapture {
async fn create(&mut self, _id: CaptureHandle, _pos: Position) -> Result<(), CaptureError> { async fn create(&mut self, _pos: Position) -> Result<(), CaptureError> {
Ok(()) Ok(())
} }
async fn destroy(&mut self, _id: CaptureHandle) -> Result<(), CaptureError> { async fn destroy(&mut self, _pos: Position) -> Result<(), CaptureError> {
Ok(()) Ok(())
} }
@@ -36,7 +33,7 @@ impl Capture for X11InputCapture {
} }
impl Stream for X11InputCapture { impl Stream for X11InputCapture {
type Item = Result<(CaptureHandle, CaptureEvent), CaptureError>; type Item = Result<(Position, CaptureEvent), CaptureError>;
fn poll_next( fn poll_next(
self: std::pin::Pin<&mut Self>, self: std::pin::Pin<&mut Self>,

View File

@@ -44,6 +44,7 @@ ashpd = { version = "0.9", default-features = false, features = [
reis = { version = "0.2", features = ["tokio"], optional = true } reis = { version = "0.2", features = ["tokio"], optional = true }
[target.'cfg(target_os="macos")'.dependencies] [target.'cfg(target_os="macos")'.dependencies]
bitflags = "2.5.0"
core-graphics = { version = "0.23", features = ["highsierra"] } core-graphics = { version = "0.23", features = ["highsierra"] }
keycode = "0.4.0" keycode = "0.4.0"

View File

@@ -1,18 +1,23 @@
use super::{error::EmulationError, Emulation, EmulationHandle}; use super::{error::EmulationError, Emulation, EmulationHandle};
use async_trait::async_trait; use async_trait::async_trait;
use bitflags::bitflags;
use core_graphics::base::CGFloat; use core_graphics::base::CGFloat;
use core_graphics::display::{ use core_graphics::display::{
CGDirectDisplayID, CGDisplayBounds, CGGetDisplaysWithRect, CGPoint, CGRect, CGSize, CGDirectDisplayID, CGDisplayBounds, CGGetDisplaysWithRect, CGPoint, CGRect, CGSize,
}; };
use core_graphics::event::{ use core_graphics::event::{
CGEvent, CGEventTapLocation, CGEventType, CGKeyCode, CGMouseButton, EventField, ScrollEventUnit, CGEvent, CGEventFlags, CGEventTapLocation, CGEventType, CGKeyCode, CGMouseButton, EventField,
ScrollEventUnit,
}; };
use core_graphics::event_source::{CGEventSource, CGEventSourceStateID}; use core_graphics::event_source::{CGEventSource, CGEventSourceStateID};
use input_event::{Event, KeyboardEvent, PointerEvent}; use input_event::{scancode, Event, KeyboardEvent, PointerEvent};
use keycode::{KeyMap, KeyMapping}; use keycode::{KeyMap, KeyMapping};
use std::cell::Cell;
use std::ops::{Index, IndexMut}; use std::ops::{Index, IndexMut};
use std::rc::Rc;
use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use tokio::task::AbortHandle; use tokio::{sync::Notify, task::JoinHandle};
use super::error::MacOSEmulationCreationError; use super::error::MacOSEmulationCreationError;
@@ -21,8 +26,10 @@ const DEFAULT_REPEAT_INTERVAL: Duration = Duration::from_millis(32);
pub(crate) struct MacOSEmulation { pub(crate) struct MacOSEmulation {
event_source: CGEventSource, event_source: CGEventSource,
repeat_task: Option<AbortHandle>, repeat_task: Option<JoinHandle<()>>,
button_state: ButtonState, button_state: ButtonState,
modifier_state: Rc<Cell<XMods>>,
notify_repeat_task: Arc<Notify>,
} }
struct ButtonState { struct ButtonState {
@@ -68,6 +75,8 @@ impl MacOSEmulation {
event_source, event_source,
button_state, button_state,
repeat_task: None, repeat_task: None,
notify_repeat_task: Arc::new(Notify::new()),
modifier_state: Rc::new(Cell::new(XMods::empty())),
}) })
} }
@@ -79,25 +88,40 @@ impl MacOSEmulation {
async fn spawn_repeat_task(&mut self, key: u16) { async fn spawn_repeat_task(&mut self, key: u16) {
// there can only be one repeating key and it's // there can only be one repeating key and it's
// always the last to be pressed // always the last to be pressed
self.kill_repeat_task(); self.cancel_repeat_task().await;
let event_source = self.event_source.clone(); let event_source = self.event_source.clone();
let notify = self.notify_repeat_task.clone();
let modifiers = self.modifier_state.clone();
let repeat_task = tokio::task::spawn_local(async move { let repeat_task = tokio::task::spawn_local(async move {
tokio::time::sleep(DEFAULT_REPEAT_DELAY).await; let stop = tokio::select! {
loop { _ = tokio::time::sleep(DEFAULT_REPEAT_DELAY) => false,
key_event(event_source.clone(), key, 1); _ = notify.notified() => true,
tokio::time::sleep(DEFAULT_REPEAT_INTERVAL).await; };
if !stop {
loop {
key_event(event_source.clone(), key, 1, modifiers.get());
tokio::select! {
_ = tokio::time::sleep(DEFAULT_REPEAT_INTERVAL) => {},
_ = notify.notified() => break,
}
}
} }
// release key when cancelled
update_modifiers(&modifiers, key as u32, 0);
key_event(event_source.clone(), key, 0, modifiers.get());
}); });
self.repeat_task = Some(repeat_task.abort_handle()); self.repeat_task = Some(repeat_task);
} }
fn kill_repeat_task(&mut self) {
async fn cancel_repeat_task(&mut self) {
if let Some(task) = self.repeat_task.take() { if let Some(task) = self.repeat_task.take() {
task.abort(); self.notify_repeat_task.notify_waiters();
let _ = task.await;
} }
} }
} }
fn key_event(event_source: CGEventSource, key: u16, state: u8) { fn key_event(event_source: CGEventSource, key: u16, state: u8, modifiers: XMods) {
let event = match CGEvent::new_keyboard_event(event_source, key, state != 0) { let event = match CGEvent::new_keyboard_event(event_source, key, state != 0) {
Ok(e) => e, Ok(e) => e,
Err(_) => { Err(_) => {
@@ -105,7 +129,21 @@ fn key_event(event_source: CGEventSource, key: u16, state: u8) {
return; return;
} }
}; };
event.set_flags(to_cgevent_flags(modifiers));
event.post(CGEventTapLocation::HID); event.post(CGEventTapLocation::HID);
log::trace!("key event: {key} {state}");
}
fn modifier_event(event_source: CGEventSource, depressed: XMods) {
let Ok(event) = CGEvent::new(event_source) else {
log::warn!("could not create CGEvent");
return;
};
let flags = to_cgevent_flags(depressed);
event.set_type(CGEventType::FlagsChanged);
event.set_flags(flags);
event.post(CGEventTapLocation::HID);
log::trace!("modifiers updated: {depressed:?}");
} }
fn get_display_at_point(x: CGFloat, y: CGFloat) -> Option<CGDirectDisplayID> { fn get_display_at_point(x: CGFloat, y: CGFloat) -> Option<CGDirectDisplayID> {
@@ -346,11 +384,25 @@ impl Emulation for MacOSEmulation {
match state { match state {
// pressed // pressed
1 => self.spawn_repeat_task(code).await, 1 => self.spawn_repeat_task(code).await,
_ => self.kill_repeat_task(), _ => self.cancel_repeat_task().await,
} }
key_event(self.event_source.clone(), code, state) update_modifiers(&self.modifier_state, key, state);
key_event(
self.event_source.clone(),
code,
state,
self.modifier_state.get(),
);
}
KeyboardEvent::Modifiers {
depressed,
latched,
locked,
group,
} => {
set_modifiers(&self.modifier_state, depressed, latched, locked, group);
modifier_event(self.event_source.clone(), self.modifier_state.get());
} }
KeyboardEvent::Modifiers { .. } => {}
}, },
} }
// FIXME // FIXME
@@ -363,3 +415,81 @@ impl Emulation for MacOSEmulation {
async fn terminate(&mut self) {} async fn terminate(&mut self) {}
} }
fn update_modifiers(modifiers: &Cell<XMods>, key: u32, state: u8) -> bool {
if let Ok(key) = scancode::Linux::try_from(key) {
let mask = match key {
scancode::Linux::KeyLeftShift | scancode::Linux::KeyRightShift => XMods::ShiftMask,
scancode::Linux::KeyCapsLock => XMods::LockMask,
scancode::Linux::KeyLeftCtrl | scancode::Linux::KeyRightCtrl => XMods::ControlMask,
scancode::Linux::KeyLeftAlt | scancode::Linux::KeyRightalt => XMods::Mod1Mask,
scancode::Linux::KeyLeftMeta | scancode::Linux::KeyRightmeta => XMods::Mod4Mask,
_ => XMods::empty(),
};
// unchanged
if mask.is_empty() {
return false;
}
let mut mods = modifiers.get();
match state {
1 => mods.insert(mask),
_ => mods.remove(mask),
}
modifiers.set(mods);
true
} else {
false
}
}
fn set_modifiers(
active_modifiers: &Cell<XMods>,
depressed: u32,
latched: u32,
locked: u32,
group: u32,
) {
let depressed = XMods::from_bits(depressed).unwrap_or_default();
let _latched = XMods::from_bits(latched).unwrap_or_default();
let _locked = XMods::from_bits(locked).unwrap_or_default();
let _group = XMods::from_bits(group).unwrap_or_default();
// we only care about the depressed modifiers for now
active_modifiers.replace(depressed);
}
fn to_cgevent_flags(depressed: XMods) -> CGEventFlags {
let mut flags = CGEventFlags::empty();
if depressed.contains(XMods::ShiftMask) {
flags |= CGEventFlags::CGEventFlagShift;
}
if depressed.contains(XMods::LockMask) {
flags |= CGEventFlags::CGEventFlagAlphaShift;
}
if depressed.contains(XMods::ControlMask) {
flags |= CGEventFlags::CGEventFlagControl;
}
if depressed.contains(XMods::Mod1Mask) {
flags |= CGEventFlags::CGEventFlagAlternate;
}
if depressed.contains(XMods::Mod4Mask) {
flags |= CGEventFlags::CGEventFlagCommand;
}
flags
}
// From X11/X.h
bitflags! {
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
struct XMods: u32 {
const ShiftMask = (1<<0);
const LockMask = (1<<1);
const ControlMask = (1<<2);
const Mod1Mask = (1<<3);
const Mod2Mask = (1<<4);
const Mod3Mask = (1<<5);
const Mod4Mask = (1<<6);
const Mod5Mask = (1<<7);
}
}

View File

@@ -44,6 +44,9 @@ rustPlatform.buildRustPackage {
postInstall = '' postInstall = ''
wrapProgram "$out/bin/lan-mouse" \ wrapProgram "$out/bin/lan-mouse" \
--set GDK_PIXBUF_MODULE_FILE ${pkgs.librsvg.out}/lib/gdk-pixbuf-2.0/2.10.0/loaders.cache --set GDK_PIXBUF_MODULE_FILE ${pkgs.librsvg.out}/lib/gdk-pixbuf-2.0/2.10.0/loaders.cache
install -Dm444 *.desktop -t $out/share/applications
install -Dm444 lan-mouse-gtk/resources/*.svg -t $out/share/icons/hicolor/scalable/apps
''; '';
meta = with lib; { meta = with lib; {

View File

@@ -11,6 +11,7 @@ pub async fn run(config: Config) -> Result<(), InputCaptureError> {
let mut input_capture = InputCapture::new(backend).await?; let mut input_capture = InputCapture::new(backend).await?;
log::info!("creating clients"); log::info!("creating clients");
input_capture.create(0, Position::Left).await?; input_capture.create(0, Position::Left).await?;
input_capture.create(4, Position::Left).await?;
input_capture.create(1, Position::Right).await?; input_capture.create(1, Position::Right).await?;
input_capture.create(2, Position::Top).await?; input_capture.create(2, Position::Top).await?;
input_capture.create(3, Position::Bottom).await?; input_capture.create(3, Position::Bottom).await?;
@@ -28,12 +29,13 @@ async fn do_capture(input_capture: &mut InputCapture) -> Result<(), CaptureError
.await .await
.ok_or(CaptureError::EndOfStream)??; .ok_or(CaptureError::EndOfStream)??;
let pos = match client { let pos = match client {
0 => Position::Left, 0 | 4 => Position::Left,
1 => Position::Right, 1 => Position::Right,
2 => Position::Top, 2 => Position::Top,
_ => Position::Bottom, 3 => Position::Bottom,
_ => panic!(),
}; };
log::info!("position: {pos}, event: {event}"); log::info!("position: {client} ({pos}), event: {event}");
if let CaptureEvent::Input(Event::Keyboard(KeyboardEvent::Key { key: 1, .. })) = event { if let CaptureEvent::Input(Event::Keyboard(KeyboardEvent::Key { key: 1, .. })) = event {
input_capture.release().await?; input_capture.release().await?;
break Ok(()); break Ok(());