mirror of
https://github.com/feschber/lan-mouse.git
synced 2026-03-13 16:20:56 +03:00
Compare commits
30 Commits
enter-hook
...
configurab
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a038a24317 | ||
|
|
a4cdc138f0 | ||
|
|
db2004fd02 | ||
|
|
3c2f9da5b7 | ||
|
|
1599e6e76a | ||
|
|
aba9ddbdcb | ||
|
|
984efe7dd7 | ||
|
|
160b2532ff | ||
|
|
51d98cc2eb | ||
|
|
b1d9180cca | ||
|
|
08f24293f8 | ||
|
|
e2162b905a | ||
|
|
2ac193a5b2 | ||
|
|
9cbe1ed8d8 | ||
|
|
3528ef4fae | ||
|
|
232c048c19 | ||
|
|
1c37579ae5 | ||
|
|
460bacade5 | ||
|
|
5fd3b719d6 | ||
|
|
e6d4585bb2 | ||
|
|
1c082d5c0c | ||
|
|
cd98acbd08 | ||
|
|
11e1919588 | ||
|
|
152bceaa86 | ||
|
|
60180d841c | ||
|
|
5802a0be0b | ||
|
|
1737727d61 | ||
|
|
ba46037a1f | ||
|
|
da768b6fb8 | ||
|
|
799b45104a |
12
Cargo.lock
generated
12
Cargo.lock
generated
@@ -1284,7 +1284,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lan-mouse"
|
name = "lan-mouse"
|
||||||
version = "0.7.3"
|
version = "0.8.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"ashpd",
|
"ashpd",
|
||||||
@@ -1292,6 +1292,7 @@ dependencies = [
|
|||||||
"async-trait",
|
"async-trait",
|
||||||
"clap",
|
"clap",
|
||||||
"core-graphics",
|
"core-graphics",
|
||||||
|
"endi",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"futures",
|
"futures",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
@@ -1311,6 +1312,7 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
"slab",
|
"slab",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
"toml",
|
"toml",
|
||||||
"wayland-client",
|
"wayland-client",
|
||||||
@@ -2010,18 +2012,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "1.0.58"
|
version = "1.0.61"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297"
|
checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror-impl",
|
"thiserror-impl",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror-impl"
|
name = "thiserror-impl"
|
||||||
version = "1.0.58"
|
version = "1.0.61"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7"
|
checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "lan-mouse"
|
name = "lan-mouse"
|
||||||
description = "Software KVM Switch / mouse & keyboard sharing software for Local Area Networks"
|
description = "Software KVM Switch / mouse & keyboard sharing software for Local Area Networks"
|
||||||
version = "0.7.3"
|
version = "0.8.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "GPL-3.0-or-later"
|
license = "GPL-3.0-or-later"
|
||||||
repository = "https://github.com/ferdinandschober/lan-mouse"
|
repository = "https://github.com/ferdinandschober/lan-mouse"
|
||||||
@@ -35,6 +35,8 @@ once_cell = "1.19.0"
|
|||||||
num_enum = "0.7.2"
|
num_enum = "0.7.2"
|
||||||
hostname = "0.4.0"
|
hostname = "0.4.0"
|
||||||
slab = "0.4.9"
|
slab = "0.4.9"
|
||||||
|
endi = "1.1.0"
|
||||||
|
thiserror = "1.0.61"
|
||||||
|
|
||||||
[target.'cfg(unix)'.dependencies]
|
[target.'cfg(unix)'.dependencies]
|
||||||
libc = "0.2.148"
|
libc = "0.2.148"
|
||||||
|
|||||||
10
README.md
10
README.md
@@ -69,7 +69,15 @@ Precompiled release binaries for Windows, MacOS and Linux are available in the [
|
|||||||
For Windows, the depenedencies are included in the .zip file, for other operating systems see [Installing Dependencies](#installing-dependencies).
|
For Windows, the depenedencies are included in the .zip file, for other operating systems see [Installing Dependencies](#installing-dependencies).
|
||||||
|
|
||||||
### Arch Linux
|
### Arch Linux
|
||||||
Lan Mouse is available on the AUR:
|
|
||||||
|
Lan Mouse can be installed from the [official repositories](https://archlinux.org/packages/extra/x86_64/lan-mouse/):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pacman -S lan-mouse
|
||||||
|
```
|
||||||
|
|
||||||
|
It is also available on the AUR:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# git version (includes latest changes)
|
# git version (includes latest changes)
|
||||||
paru -S lan-mouse-git
|
paru -S lan-mouse-git
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
# example configuration
|
# example configuration
|
||||||
|
|
||||||
|
# capture_backend = "LayerShell"
|
||||||
|
|
||||||
# release bind
|
# release bind
|
||||||
release_bind = [ "KeyA", "KeyS", "KeyD", "KeyF" ]
|
release_bind = [ "KeyA", "KeyS", "KeyD", "KeyF" ]
|
||||||
|
|
||||||
|
|||||||
12
flake.lock
generated
12
flake.lock
generated
@@ -20,11 +20,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1710806803,
|
"lastModified": 1716293225,
|
||||||
"narHash": "sha256-qrxvLS888pNJFwJdK+hf1wpRCSQcqA6W5+Ox202NDa0=",
|
"narHash": "sha256-pU9ViBVE3XYb70xZx+jK6SEVphvt7xMTbm6yDIF4xPs=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "b06025f1533a1e07b6db3e75151caa155d1c7eb3",
|
"rev": "3eaeaeb6b1e08a016380c279f8846e0bd8808916",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -48,11 +48,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1710987136,
|
"lastModified": 1716257780,
|
||||||
"narHash": "sha256-Q8GRdlAIKZ8tJUXrbcRO1pA33AdoPfTUirsSnmGQnOU=",
|
"narHash": "sha256-R+NjvJzKEkTVCmdrKRfPE4liX/KMGVqGUwwS5H8ET8A=",
|
||||||
"owner": "oxalica",
|
"owner": "oxalica",
|
||||||
"repo": "rust-overlay",
|
"repo": "rust-overlay",
|
||||||
"rev": "97596b54ac34ad8184ca1eef44b1ec2e5c2b5f9e",
|
"rev": "4e5e3d2c5c9b2721bd266f9e43c14e96811b89d2",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|||||||
@@ -2,10 +2,14 @@
|
|||||||
rustPlatform,
|
rustPlatform,
|
||||||
lib,
|
lib,
|
||||||
pkgs,
|
pkgs,
|
||||||
}:
|
}: let
|
||||||
|
cargoToml = builtins.fromTOML (builtins.readFile ../Cargo.toml);
|
||||||
|
pname = cargoToml.package.name;
|
||||||
|
version = cargoToml.package.version;
|
||||||
|
in
|
||||||
rustPlatform.buildRustPackage {
|
rustPlatform.buildRustPackage {
|
||||||
pname = "lan-mouse";
|
pname = pname;
|
||||||
version = "0.7.0";
|
version = version;
|
||||||
|
|
||||||
nativeBuildInputs = with pkgs; [
|
nativeBuildInputs = with pkgs; [
|
||||||
pkg-config
|
pkg-config
|
||||||
@@ -23,7 +27,7 @@ rustPlatform.buildRustPackage {
|
|||||||
];
|
];
|
||||||
|
|
||||||
src = builtins.path {
|
src = builtins.path {
|
||||||
name = "lan-mouse";
|
name = pname;
|
||||||
path = lib.cleanSource ../.;
|
path = lib.cleanSource ../.;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -38,7 +42,7 @@ rustPlatform.buildRustPackage {
|
|||||||
Lan Mouse is a mouse and keyboard sharing software similar to universal-control on Apple devices. It allows for using multiple pcs with a single set of mouse and keyboard. This is also known as a Software KVM switch.
|
Lan Mouse is a mouse and keyboard sharing software similar to universal-control on Apple devices. It allows for using multiple pcs with a single set of mouse and keyboard. This is also known as a Software KVM switch.
|
||||||
The primary target is Wayland on Linux but Windows and MacOS and Linux on Xorg have partial support as well (see below for more details).
|
The primary target is Wayland on Linux but Windows and MacOS and Linux on Xorg have partial support as well (see below for more details).
|
||||||
'';
|
'';
|
||||||
mainProgram = "lan-mouse";
|
mainProgram = pname;
|
||||||
platforms = platforms.all;
|
platforms = platforms.all;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,14 @@ use futures_core::Stream;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
client::{ClientEvent, ClientHandle},
|
client::{ClientEvent, ClientHandle},
|
||||||
|
config::CaptureBackend,
|
||||||
event::Event,
|
event::Event,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use self::error::CaptureCreationError;
|
||||||
|
|
||||||
|
pub mod error;
|
||||||
|
|
||||||
#[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
|
#[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
|
||||||
pub mod libei;
|
pub mod libei;
|
||||||
|
|
||||||
@@ -25,48 +30,57 @@ pub mod x11;
|
|||||||
/// fallback input capture (does not produce events)
|
/// fallback input capture (does not produce events)
|
||||||
pub mod dummy;
|
pub mod dummy;
|
||||||
|
|
||||||
pub async fn create() -> Box<dyn InputCapture<Item = io::Result<(ClientHandle, Event)>>> {
|
pub async fn create_backend(
|
||||||
#[cfg(target_os = "macos")]
|
backend: CaptureBackend,
|
||||||
match macos::MacOSInputCapture::new() {
|
) -> Result<Box<dyn InputCapture<Item = io::Result<(ClientHandle, Event)>>>, CaptureCreationError> {
|
||||||
Ok(p) => return Box::new(p),
|
match backend {
|
||||||
Err(e) => log::info!("macos input capture not available: {e}"),
|
#[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
|
||||||
|
CaptureBackend::InputCapturePortal => Ok(Box::new(libei::LibeiInputCapture::new().await?)),
|
||||||
|
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
|
||||||
|
CaptureBackend::LayerShell => Ok(Box::new(wayland::WaylandInputCapture::new()?)),
|
||||||
|
#[cfg(all(unix, feature = "x11", not(target_os = "macos")))]
|
||||||
|
CaptureBackend::X11 => Ok(Box::new(x11::X11InputCapture::new()?)),
|
||||||
|
#[cfg(windows)]
|
||||||
|
CaptureBackend::Windows => Ok(Box::new(windows::WindowsInputCapture::new())),
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
CaptureBackend::MacOs => Ok(Box::new(macos::MacOSInputCapture::new()?)),
|
||||||
|
CaptureBackend::Dummy => Ok(Box::new(dummy::DummyInputCapture::new())),
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
pub async fn create(
|
||||||
match windows::WindowsInputCapture::new() {
|
backend: Option<CaptureBackend>,
|
||||||
Ok(p) => return Box::new(p),
|
) -> Result<Box<dyn InputCapture<Item = io::Result<(ClientHandle, Event)>>>, CaptureCreationError> {
|
||||||
Err(e) => log::info!("windows input capture not available: {e}"),
|
if let Some(backend) = backend {
|
||||||
}
|
let b = create_backend(backend).await;
|
||||||
|
if b.is_ok() {
|
||||||
#[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
|
log::info!("using capture backend: {backend}");
|
||||||
match libei::LibeiInputCapture::new().await {
|
|
||||||
Ok(p) => {
|
|
||||||
log::info!("using libei input capture");
|
|
||||||
return Box::new(p);
|
|
||||||
}
|
}
|
||||||
Err(e) => log::info!("libei input capture not available: {e}"),
|
return b;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
|
for backend in [
|
||||||
match wayland::WaylandInputCapture::new() {
|
#[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
|
||||||
Ok(p) => {
|
CaptureBackend::InputCapturePortal,
|
||||||
log::info!("using layer-shell input capture");
|
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
|
||||||
return Box::new(p);
|
CaptureBackend::LayerShell,
|
||||||
|
#[cfg(all(unix, feature = "x11", not(target_os = "macos")))]
|
||||||
|
CaptureBackend::X11,
|
||||||
|
#[cfg(windows)]
|
||||||
|
CaptureBackend::Windows,
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
CaptureBackend::MacOs,
|
||||||
|
CaptureBackend::Dummy,
|
||||||
|
] {
|
||||||
|
match create_backend(backend).await {
|
||||||
|
Ok(b) => {
|
||||||
|
log::info!("using capture backend: {backend}");
|
||||||
|
return Ok(b);
|
||||||
|
}
|
||||||
|
Err(e) => log::warn!("{backend} input capture backend unavailable: {e}"),
|
||||||
}
|
}
|
||||||
Err(e) => log::info!("layer_shell input capture not available: {e}"),
|
|
||||||
}
|
}
|
||||||
|
Err(CaptureCreationError::NoAvailableBackend)
|
||||||
#[cfg(all(unix, feature = "x11", not(target_os = "macos")))]
|
|
||||||
match x11::X11InputCapture::new() {
|
|
||||||
Ok(p) => {
|
|
||||||
log::info!("using x11 input capture");
|
|
||||||
return Box::new(p);
|
|
||||||
}
|
|
||||||
Err(e) => log::info!("x11 input capture not available: {e}"),
|
|
||||||
}
|
|
||||||
|
|
||||||
log::error!("falling back to dummy input capture");
|
|
||||||
Box::new(dummy::DummyInputCapture::new())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait InputCapture: Stream<Item = io::Result<(ClientHandle, Event)>> + Unpin {
|
pub trait InputCapture: Stream<Item = io::Result<(ClientHandle, Event)>> + Unpin {
|
||||||
|
|||||||
142
src/capture/error.rs
Normal file
142
src/capture/error.rs
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
use std::fmt::Display;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
|
||||||
|
use std::io;
|
||||||
|
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
|
||||||
|
use wayland_client::{
|
||||||
|
backend::WaylandError,
|
||||||
|
globals::{BindError, GlobalError},
|
||||||
|
ConnectError, DispatchError,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum CaptureCreationError {
|
||||||
|
NoAvailableBackend,
|
||||||
|
#[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
|
||||||
|
Libei(#[from] LibeiCaptureCreationError),
|
||||||
|
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
|
||||||
|
LayerShell(#[from] LayerShellCaptureCreationError),
|
||||||
|
#[cfg(all(unix, feature = "x11", not(target_os = "macos")))]
|
||||||
|
X11(#[from] X11InputCaptureCreationError),
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
Macos(#[from] MacOSInputCaptureCreationError),
|
||||||
|
#[cfg(windows)]
|
||||||
|
Windows,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for CaptureCreationError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let reason = match self {
|
||||||
|
#[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
|
||||||
|
CaptureCreationError::Libei(reason) => {
|
||||||
|
format!("error creating portal backend: {reason}")
|
||||||
|
}
|
||||||
|
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
|
||||||
|
CaptureCreationError::LayerShell(reason) => {
|
||||||
|
format!("error creating layer-shell backend: {reason}")
|
||||||
|
}
|
||||||
|
#[cfg(all(unix, feature = "x11", not(target_os = "macos")))]
|
||||||
|
CaptureCreationError::X11(e) => format!("{e}"),
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
CaptureCreationError::Macos(e) => format!("{e}"),
|
||||||
|
#[cfg(windows)]
|
||||||
|
CaptureCreationError::Windows => String::new(),
|
||||||
|
CaptureCreationError::NoAvailableBackend => "no available backend".to_string(),
|
||||||
|
};
|
||||||
|
write!(f, "could not create input capture: {reason}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum LibeiCaptureCreationError {
|
||||||
|
Ashpd(#[from] ashpd::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
|
||||||
|
impl Display for LibeiCaptureCreationError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
LibeiCaptureCreationError::Ashpd(portal_error) => write!(f, "{portal_error}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub struct WaylandBindError {
|
||||||
|
inner: BindError,
|
||||||
|
protocol: &'static str,
|
||||||
|
}
|
||||||
|
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
|
||||||
|
impl WaylandBindError {
|
||||||
|
pub(crate) fn new(inner: BindError, protocol: &'static str) -> Self {
|
||||||
|
Self { inner, protocol }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
|
||||||
|
impl Display for WaylandBindError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{} protocol not supported: {}",
|
||||||
|
self.protocol, self.inner
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum LayerShellCaptureCreationError {
|
||||||
|
Connect(#[from] ConnectError),
|
||||||
|
Global(#[from] GlobalError),
|
||||||
|
Wayland(#[from] WaylandError),
|
||||||
|
Bind(#[from] WaylandBindError),
|
||||||
|
Dispatch(#[from] DispatchError),
|
||||||
|
Io(#[from] io::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
|
||||||
|
impl Display for LayerShellCaptureCreationError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
LayerShellCaptureCreationError::Bind(e) => write!(f, "{e}"),
|
||||||
|
LayerShellCaptureCreationError::Connect(e) => {
|
||||||
|
write!(f, "could not connect to wayland compositor: {e}")
|
||||||
|
}
|
||||||
|
LayerShellCaptureCreationError::Global(e) => write!(f, "wayland error: {e}"),
|
||||||
|
LayerShellCaptureCreationError::Wayland(e) => write!(f, "wayland error: {e}"),
|
||||||
|
LayerShellCaptureCreationError::Dispatch(e) => {
|
||||||
|
write!(f, "error dispatching wayland events: {e}")
|
||||||
|
}
|
||||||
|
LayerShellCaptureCreationError::Io(e) => write!(f, "io error: {e}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(unix, feature = "x11", not(target_os = "macos")))]
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum X11InputCaptureCreationError {
|
||||||
|
NotImplemented,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(unix, feature = "x11", not(target_os = "macos")))]
|
||||||
|
impl Display for X11InputCaptureCreationError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "X11 input capture is not yet implemented :(")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum MacOSInputCaptureCreationError {
|
||||||
|
NotImplemented,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
impl Display for MacOSInputCaptureCreationError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "macos input capture is not yet implemented :(")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -36,6 +36,8 @@ use crate::{
|
|||||||
event::{Event, KeyboardEvent, PointerEvent},
|
event::{Event, KeyboardEvent, PointerEvent},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use super::error::LibeiCaptureCreationError;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum ProducerEvent {
|
enum ProducerEvent {
|
||||||
Release,
|
Release,
|
||||||
@@ -131,7 +133,7 @@ impl<'a> Drop for LibeiInputCapture<'a> {
|
|||||||
|
|
||||||
async fn create_session<'a>(
|
async fn create_session<'a>(
|
||||||
input_capture: &'a InputCapture<'a>,
|
input_capture: &'a InputCapture<'a>,
|
||||||
) -> Result<(Session<'a>, BitFlags<Capabilities>)> {
|
) -> std::result::Result<(Session<'a>, BitFlags<Capabilities>), ashpd::Error> {
|
||||||
log::debug!("creating input capture session");
|
log::debug!("creating input capture session");
|
||||||
let (session, capabilities) = loop {
|
let (session, capabilities) = loop {
|
||||||
match input_capture
|
match input_capture
|
||||||
@@ -213,7 +215,7 @@ async fn wait_for_active_client(
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> LibeiInputCapture<'a> {
|
impl<'a> LibeiInputCapture<'a> {
|
||||||
pub async fn new() -> Result<Self> {
|
pub async fn new() -> std::result::Result<Self, LibeiCaptureCreationError> {
|
||||||
let input_capture = Box::pin(InputCapture::new().await?);
|
let input_capture = Box::pin(InputCapture::new().await?);
|
||||||
let input_capture_ptr = input_capture.as_ref().get_ref() as *const InputCapture<'static>;
|
let input_capture_ptr = input_capture.as_ref().get_ref() as *const InputCapture<'static>;
|
||||||
let mut first_session = Some(create_session(unsafe { &*input_capture_ptr }).await?);
|
let mut first_session = Some(create_session(unsafe { &*input_capture_ptr }).await?);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
|
use crate::capture::error::MacOSInputCaptureCreationError;
|
||||||
use crate::capture::InputCapture;
|
use crate::capture::InputCapture;
|
||||||
use crate::client::{ClientEvent, ClientHandle};
|
use crate::client::{ClientEvent, ClientHandle};
|
||||||
use crate::event::Event;
|
use crate::event::Event;
|
||||||
use anyhow::{anyhow, Result};
|
|
||||||
use futures_core::Stream;
|
use futures_core::Stream;
|
||||||
use std::task::{Context, Poll};
|
use std::task::{Context, Poll};
|
||||||
use std::{io, pin::Pin};
|
use std::{io, pin::Pin};
|
||||||
@@ -9,8 +9,8 @@ use std::{io, pin::Pin};
|
|||||||
pub struct MacOSInputCapture;
|
pub struct MacOSInputCapture;
|
||||||
|
|
||||||
impl MacOSInputCapture {
|
impl MacOSInputCapture {
|
||||||
pub fn new() -> Result<Self> {
|
pub fn new() -> std::result::Result<Self, MacOSInputCaptureCreationError> {
|
||||||
Err(anyhow!("not yet implemented"))
|
Err(MacOSInputCaptureCreationError::NotImplemented)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
capture::InputCapture,
|
capture::{error::WaylandBindError, InputCapture},
|
||||||
client::{ClientEvent, ClientHandle, Position},
|
client::{ClientEvent, ClientHandle, Position},
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
|
||||||
use futures_core::Stream;
|
use futures_core::Stream;
|
||||||
use memmap::MmapOptions;
|
use memmap::MmapOptions;
|
||||||
use std::{
|
use std::{
|
||||||
@@ -68,6 +67,8 @@ use tempfile;
|
|||||||
|
|
||||||
use crate::event::{Event, KeyboardEvent, PointerEvent};
|
use crate::event::{Event, KeyboardEvent, PointerEvent};
|
||||||
|
|
||||||
|
use super::error::LayerShellCaptureCreationError;
|
||||||
|
|
||||||
struct Globals {
|
struct Globals {
|
||||||
compositor: wl_compositor::WlCompositor,
|
compositor: wl_compositor::WlCompositor,
|
||||||
pointer_constraints: ZwpPointerConstraintsV1,
|
pointer_constraints: ZwpPointerConstraintsV1,
|
||||||
@@ -111,6 +112,7 @@ struct State {
|
|||||||
qh: QueueHandle<Self>,
|
qh: QueueHandle<Self>,
|
||||||
pending_events: VecDeque<(ClientHandle, Event)>,
|
pending_events: VecDeque<(ClientHandle, Event)>,
|
||||||
output_info: Vec<(WlOutput, OutputInfo)>,
|
output_info: Vec<(WlOutput, OutputInfo)>,
|
||||||
|
scroll_discrete_pending: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Inner {
|
struct Inner {
|
||||||
@@ -257,64 +259,37 @@ fn draw(f: &mut File, (width, height): (u32, u32)) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl WaylandInputCapture {
|
impl WaylandInputCapture {
|
||||||
pub fn new() -> Result<Self> {
|
pub fn new() -> std::result::Result<Self, LayerShellCaptureCreationError> {
|
||||||
let conn = match Connection::connect_to_env() {
|
let conn = Connection::connect_to_env()?;
|
||||||
Ok(c) => c,
|
let (g, mut queue) = registry_queue_init::<State>(&conn)?;
|
||||||
Err(e) => return Err(anyhow!("could not connect to wayland compositor: {e:?}")),
|
|
||||||
};
|
|
||||||
|
|
||||||
let (g, mut queue) = match registry_queue_init::<State>(&conn) {
|
|
||||||
Ok(q) => q,
|
|
||||||
Err(e) => return Err(anyhow!("failed to initialize wl_registry: {e:?}")),
|
|
||||||
};
|
|
||||||
|
|
||||||
let qh = queue.handle();
|
let qh = queue.handle();
|
||||||
|
|
||||||
let compositor: wl_compositor::WlCompositor = match g.bind(&qh, 4..=5, ()) {
|
let compositor: wl_compositor::WlCompositor = g
|
||||||
Ok(compositor) => compositor,
|
.bind(&qh, 4..=5, ())
|
||||||
Err(_) => return Err(anyhow!("wl_compositor >= v4 not supported")),
|
.map_err(|e| WaylandBindError::new(e, "wl_compositor 4..=5"))?;
|
||||||
};
|
let xdg_output_manager: ZxdgOutputManagerV1 = g
|
||||||
|
.bind(&qh, 1..=3, ())
|
||||||
let xdg_output_manager: ZxdgOutputManagerV1 = match g.bind(&qh, 1..=3, ()) {
|
.map_err(|e| WaylandBindError::new(e, "xdg_output_manager 1..=3"))?;
|
||||||
Ok(xdg_output_manager) => xdg_output_manager,
|
let shm: wl_shm::WlShm = g
|
||||||
Err(_) => return Err(anyhow!("xdg_output not supported!")),
|
.bind(&qh, 1..=1, ())
|
||||||
};
|
.map_err(|e| WaylandBindError::new(e, "wl_shm"))?;
|
||||||
|
let layer_shell: ZwlrLayerShellV1 = g
|
||||||
let shm: wl_shm::WlShm = match g.bind(&qh, 1..=1, ()) {
|
.bind(&qh, 3..=4, ())
|
||||||
Ok(wl_shm) => wl_shm,
|
.map_err(|e| WaylandBindError::new(e, "wlr_layer_shell 3..=4"))?;
|
||||||
Err(_) => return Err(anyhow!("wl_shm v1 not supported")),
|
let seat: wl_seat::WlSeat = g
|
||||||
};
|
.bind(&qh, 7..=8, ())
|
||||||
|
.map_err(|e| WaylandBindError::new(e, "wl_seat 7..=8"))?;
|
||||||
let layer_shell: ZwlrLayerShellV1 = match g.bind(&qh, 3..=4, ()) {
|
|
||||||
Ok(layer_shell) => layer_shell,
|
|
||||||
Err(_) => return Err(anyhow!("zwlr_layer_shell_v1 >= v3 not supported - required to display a surface at the edge of the screen")),
|
|
||||||
};
|
|
||||||
|
|
||||||
let seat: wl_seat::WlSeat = match g.bind(&qh, 7..=8, ()) {
|
|
||||||
Ok(wl_seat) => wl_seat,
|
|
||||||
Err(_) => return Err(anyhow!("wl_seat >= v7 not supported")),
|
|
||||||
};
|
|
||||||
|
|
||||||
let pointer_constraints: ZwpPointerConstraintsV1 = match g.bind(&qh, 1..=1, ()) {
|
|
||||||
Ok(pointer_constraints) => pointer_constraints,
|
|
||||||
Err(_) => return Err(anyhow!("zwp_pointer_constraints_v1 not supported")),
|
|
||||||
};
|
|
||||||
|
|
||||||
let relative_pointer_manager: ZwpRelativePointerManagerV1 = match g.bind(&qh, 1..=1, ()) {
|
|
||||||
Ok(relative_pointer_manager) => relative_pointer_manager,
|
|
||||||
Err(_) => return Err(anyhow!("zwp_relative_pointer_manager_v1 not supported")),
|
|
||||||
};
|
|
||||||
|
|
||||||
let shortcut_inhibit_manager: ZwpKeyboardShortcutsInhibitManagerV1 =
|
|
||||||
match g.bind(&qh, 1..=1, ()) {
|
|
||||||
Ok(shortcut_inhibit_manager) => shortcut_inhibit_manager,
|
|
||||||
Err(_) => {
|
|
||||||
return Err(anyhow!(
|
|
||||||
"zwp_keyboard_shortcuts_inhibit_manager_v1 not supported"
|
|
||||||
))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
let pointer_constraints: ZwpPointerConstraintsV1 = g
|
||||||
|
.bind(&qh, 1..=1, ())
|
||||||
|
.map_err(|e| WaylandBindError::new(e, "zwp_pointer_constraints_v1"))?;
|
||||||
|
let relative_pointer_manager: ZwpRelativePointerManagerV1 = g
|
||||||
|
.bind(&qh, 1..=1, ())
|
||||||
|
.map_err(|e| WaylandBindError::new(e, "zwp_relative_pointer_manager_v1"))?;
|
||||||
|
let shortcut_inhibit_manager: ZwpKeyboardShortcutsInhibitManagerV1 = g
|
||||||
|
.bind(&qh, 1..=1, ())
|
||||||
|
.map_err(|e| WaylandBindError::new(e, "zwp_keyboard_shortcuts_inhibit_manager_v1"))?;
|
||||||
let outputs = vec![];
|
let outputs = vec![];
|
||||||
|
|
||||||
let g = Globals {
|
let g = Globals {
|
||||||
@@ -351,6 +326,7 @@ impl WaylandInputCapture {
|
|||||||
read_guard: None,
|
read_guard: None,
|
||||||
pending_events: VecDeque::new(),
|
pending_events: VecDeque::new(),
|
||||||
output_info: vec![],
|
output_info: vec![],
|
||||||
|
scroll_discrete_pending: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
// dispatch registry to () again, in order to read all wl_outputs
|
// dispatch registry to () again, in order to read all wl_outputs
|
||||||
@@ -752,17 +728,25 @@ 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 (_, client) = app.focused.as_ref().unwrap();
|
||||||
app.pending_events.push_back((
|
if app.scroll_discrete_pending {
|
||||||
*client,
|
// each axisvalue120 event is coupled with
|
||||||
Event::Pointer(PointerEvent::Axis {
|
// a corresponding axis event, which needs to
|
||||||
time,
|
// be ignored to not duplicate the scrolling
|
||||||
axis: u32::from(axis) as u8,
|
app.scroll_discrete_pending = false;
|
||||||
value,
|
} else {
|
||||||
}),
|
app.pending_events.push_back((
|
||||||
));
|
*client,
|
||||||
|
Event::Pointer(PointerEvent::Axis {
|
||||||
|
time,
|
||||||
|
axis: u32::from(axis) as u8,
|
||||||
|
value,
|
||||||
|
}),
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
wl_pointer::Event::AxisValue120 { axis, value120 } => {
|
wl_pointer::Event::AxisValue120 { axis, value120 } => {
|
||||||
let (_, client) = app.focused.as_ref().unwrap();
|
let (_, client) = app.focused.as_ref().unwrap();
|
||||||
|
app.scroll_discrete_pending = true;
|
||||||
app.pending_events.push_back((
|
app.pending_events.push_back((
|
||||||
*client,
|
*client,
|
||||||
Event::Pointer(PointerEvent::AxisDiscrete120 {
|
Event::Pointer(PointerEvent::AxisDiscrete120 {
|
||||||
|
|||||||
@@ -96,8 +96,7 @@ unsafe fn get_event_tid() -> Option<u32> {
|
|||||||
static mut ENTRY_POINT: (i32, i32) = (0, 0);
|
static mut ENTRY_POINT: (i32, i32) = (0, 0);
|
||||||
|
|
||||||
fn to_mouse_event(wparam: WPARAM, lparam: LPARAM) -> Option<PointerEvent> {
|
fn to_mouse_event(wparam: WPARAM, lparam: LPARAM) -> Option<PointerEvent> {
|
||||||
let mouse_low_level: MSLLHOOKSTRUCT =
|
let mouse_low_level: MSLLHOOKSTRUCT = unsafe { *(lparam.0 as *const MSLLHOOKSTRUCT) };
|
||||||
unsafe { *std::mem::transmute::<LPARAM, *const MSLLHOOKSTRUCT>(lparam) };
|
|
||||||
match wparam {
|
match wparam {
|
||||||
WPARAM(p) if p == WM_LBUTTONDOWN as usize => Some(PointerEvent::Button {
|
WPARAM(p) if p == WM_LBUTTONDOWN as usize => Some(PointerEvent::Button {
|
||||||
time: 0,
|
time: 0,
|
||||||
@@ -141,7 +140,7 @@ fn to_mouse_event(wparam: WPARAM, lparam: LPARAM) -> Option<PointerEvent> {
|
|||||||
},
|
},
|
||||||
WPARAM(p) if p == WM_MOUSEWHEEL as usize => Some(PointerEvent::AxisDiscrete120 {
|
WPARAM(p) if p == WM_MOUSEWHEEL as usize => Some(PointerEvent::AxisDiscrete120 {
|
||||||
axis: 0,
|
axis: 0,
|
||||||
value: -(mouse_low_level.mouseData as i32),
|
value: -(mouse_low_level.mouseData as i32 >> 16),
|
||||||
}),
|
}),
|
||||||
WPARAM(p) if p == WM_XBUTTONDOWN as usize || p == WM_XBUTTONUP as usize => {
|
WPARAM(p) if p == WM_XBUTTONDOWN as usize || p == WM_XBUTTONUP as usize => {
|
||||||
let hb = mouse_low_level.mouseData >> 16;
|
let hb = mouse_low_level.mouseData >> 16;
|
||||||
@@ -167,8 +166,7 @@ fn to_mouse_event(wparam: WPARAM, lparam: LPARAM) -> Option<PointerEvent> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn to_key_event(wparam: WPARAM, lparam: LPARAM) -> Option<KeyboardEvent> {
|
unsafe fn to_key_event(wparam: WPARAM, lparam: LPARAM) -> Option<KeyboardEvent> {
|
||||||
let kybrdllhookstruct: KBDLLHOOKSTRUCT =
|
let kybrdllhookstruct: KBDLLHOOKSTRUCT = *(lparam.0 as *const KBDLLHOOKSTRUCT);
|
||||||
*std::mem::transmute::<LPARAM, *const KBDLLHOOKSTRUCT>(lparam);
|
|
||||||
let mut scan_code = kybrdllhookstruct.scanCode;
|
let mut scan_code = kybrdllhookstruct.scanCode;
|
||||||
log::trace!("scan_code: {scan_code}");
|
log::trace!("scan_code: {scan_code}");
|
||||||
if kybrdllhookstruct.flags.contains(LLKHF_EXTENDED) {
|
if kybrdllhookstruct.flags.contains(LLKHF_EXTENDED) {
|
||||||
@@ -247,8 +245,7 @@ unsafe fn check_client_activation(wparam: WPARAM, lparam: LPARAM) -> bool {
|
|||||||
if wparam.0 != WM_MOUSEMOVE as usize {
|
if wparam.0 != WM_MOUSEMOVE as usize {
|
||||||
return ACTIVE_CLIENT.is_some();
|
return ACTIVE_CLIENT.is_some();
|
||||||
}
|
}
|
||||||
let mouse_low_level: MSLLHOOKSTRUCT =
|
let mouse_low_level: MSLLHOOKSTRUCT = *(lparam.0 as *const MSLLHOOKSTRUCT);
|
||||||
unsafe { *std::mem::transmute::<LPARAM, *const MSLLHOOKSTRUCT>(lparam) };
|
|
||||||
static mut PREV_POS: Option<(i32, i32)> = None;
|
static mut PREV_POS: Option<(i32, i32)> = None;
|
||||||
let curr_pos = (mouse_low_level.pt.x, mouse_low_level.pt.y);
|
let curr_pos = (mouse_low_level.pt.x, mouse_low_level.pt.y);
|
||||||
let prev_pos = PREV_POS.unwrap_or(curr_pos);
|
let prev_pos = PREV_POS.unwrap_or(curr_pos);
|
||||||
@@ -578,7 +575,7 @@ fn update_clients(client_event: ClientEvent) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl WindowsInputCapture {
|
impl WindowsInputCapture {
|
||||||
pub(crate) fn new() -> Result<Self> {
|
pub(crate) fn new() -> Self {
|
||||||
unsafe {
|
unsafe {
|
||||||
let (tx, rx) = channel(10);
|
let (tx, rx) = channel(10);
|
||||||
EVENT_TX.replace(tx);
|
EVENT_TX.replace(tx);
|
||||||
@@ -586,10 +583,10 @@ impl WindowsInputCapture {
|
|||||||
let msg_thread = Some(thread::spawn(|| message_thread(ready_tx)));
|
let msg_thread = Some(thread::spawn(|| message_thread(ready_tx)));
|
||||||
/* wait for thread to set its id */
|
/* wait for thread to set its id */
|
||||||
ready_rx.recv().expect("channel closed");
|
ready_rx.recv().expect("channel closed");
|
||||||
Ok(Self {
|
Self {
|
||||||
msg_thread,
|
msg_thread,
|
||||||
event_rx: rx,
|
event_rx: rx,
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
use anyhow::{anyhow, Result};
|
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::task::Poll;
|
use std::task::Poll;
|
||||||
|
|
||||||
@@ -9,11 +8,13 @@ use crate::event::Event;
|
|||||||
|
|
||||||
use crate::client::{ClientEvent, ClientHandle};
|
use crate::client::{ClientEvent, ClientHandle};
|
||||||
|
|
||||||
|
use super::error::X11InputCaptureCreationError;
|
||||||
|
|
||||||
pub struct X11InputCapture {}
|
pub struct X11InputCapture {}
|
||||||
|
|
||||||
impl X11InputCapture {
|
impl X11InputCapture {
|
||||||
pub fn new() -> Result<Self> {
|
pub fn new() -> std::result::Result<Self, X11InputCaptureCreationError> {
|
||||||
Err(anyhow!("not implemented"))
|
Err(X11InputCaptureCreationError::NotImplemented)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use crate::capture;
|
use crate::capture;
|
||||||
use crate::client::{ClientEvent, Position};
|
use crate::client::{ClientEvent, Position};
|
||||||
|
use crate::config::Config;
|
||||||
use crate::event::{Event, KeyboardEvent};
|
use crate::event::{Event, KeyboardEvent};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
@@ -12,12 +13,14 @@ pub fn run() -> Result<()> {
|
|||||||
.enable_time()
|
.enable_time()
|
||||||
.build()?;
|
.build()?;
|
||||||
|
|
||||||
runtime.block_on(LocalSet::new().run_until(input_capture_test()))
|
let config = Config::new()?;
|
||||||
|
|
||||||
|
runtime.block_on(LocalSet::new().run_until(input_capture_test(config)))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn input_capture_test() -> Result<()> {
|
async fn input_capture_test(config: Config) -> Result<()> {
|
||||||
log::info!("creating input capture");
|
log::info!("creating input capture");
|
||||||
let mut input_capture = capture::create().await;
|
let mut input_capture = capture::create(config.capture_backend).await?;
|
||||||
log::info!("creating clients");
|
log::info!("creating clients");
|
||||||
input_capture.notify(ClientEvent::Create(0, Position::Left))?;
|
input_capture.notify(ClientEvent::Create(0, Position::Left))?;
|
||||||
input_capture.notify(ClientEvent::Create(1, Position::Right))?;
|
input_capture.notify(ClientEvent::Create(1, Position::Right))?;
|
||||||
|
|||||||
145
src/config.rs
145
src/config.rs
@@ -1,8 +1,9 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clap::Parser;
|
use clap::{Parser, ValueEnum};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::env;
|
use std::env;
|
||||||
|
use std::fmt::Display;
|
||||||
use std::net::IpAddr;
|
use std::net::IpAddr;
|
||||||
use std::{error::Error, fs};
|
use std::{error::Error, fs};
|
||||||
use toml;
|
use toml;
|
||||||
@@ -15,8 +16,10 @@ pub const DEFAULT_PORT: u16 = 4242;
|
|||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
pub struct ConfigToml {
|
pub struct ConfigToml {
|
||||||
|
pub capture_backend: Option<CaptureBackend>,
|
||||||
|
pub emulation_backend: Option<EmulationBackend>,
|
||||||
pub port: Option<u16>,
|
pub port: Option<u16>,
|
||||||
pub frontend: Option<String>,
|
pub frontend: Option<Frontend>,
|
||||||
pub release_bind: Option<Vec<scancode::Linux>>,
|
pub release_bind: Option<Vec<scancode::Linux>>,
|
||||||
pub left: Option<TomlClient>,
|
pub left: Option<TomlClient>,
|
||||||
pub right: Option<TomlClient>,
|
pub right: Option<TomlClient>,
|
||||||
@@ -26,6 +29,7 @@ pub struct ConfigToml {
|
|||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
|
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
|
||||||
pub struct TomlClient {
|
pub struct TomlClient {
|
||||||
|
pub capture_backend: Option<CaptureBackend>,
|
||||||
pub hostname: Option<String>,
|
pub hostname: Option<String>,
|
||||||
pub host_name: Option<String>,
|
pub host_name: Option<String>,
|
||||||
pub ips: Option<Vec<IpAddr>>,
|
pub ips: Option<Vec<IpAddr>>,
|
||||||
@@ -51,7 +55,7 @@ struct CliArgs {
|
|||||||
|
|
||||||
/// the frontend to use [cli | gtk]
|
/// the frontend to use [cli | gtk]
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
frontend: Option<String>,
|
frontend: Option<Frontend>,
|
||||||
|
|
||||||
/// non-default config file location
|
/// non-default config file location
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
@@ -68,16 +72,106 @@ struct CliArgs {
|
|||||||
/// test input emulation
|
/// test input emulation
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
test_emulation: bool,
|
test_emulation: bool,
|
||||||
|
|
||||||
|
/// capture backend override
|
||||||
|
#[arg(long)]
|
||||||
|
capture_backend: Option<CaptureBackend>,
|
||||||
|
|
||||||
|
/// emulation backend override
|
||||||
|
#[arg(long)]
|
||||||
|
emulation_backend: Option<EmulationBackend>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, ValueEnum)]
|
||||||
|
pub enum CaptureBackend {
|
||||||
|
#[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
|
||||||
|
InputCapturePortal,
|
||||||
|
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
|
||||||
|
LayerShell,
|
||||||
|
#[cfg(all(unix, feature = "x11", not(target_os = "macos")))]
|
||||||
|
X11,
|
||||||
|
#[cfg(windows)]
|
||||||
|
Windows,
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
MacOs,
|
||||||
|
Dummy,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for CaptureBackend {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
#[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
|
||||||
|
CaptureBackend::InputCapturePortal => write!(f, "input-capture-portal"),
|
||||||
|
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
|
||||||
|
CaptureBackend::LayerShell => write!(f, "layer-shell"),
|
||||||
|
#[cfg(all(unix, feature = "x11", not(target_os = "macos")))]
|
||||||
|
CaptureBackend::X11 => write!(f, "X11"),
|
||||||
|
#[cfg(windows)]
|
||||||
|
CaptureBackend::Windows => write!(f, "windows"),
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
CaptureBackend::MacOs => write!(f, "MacOS"),
|
||||||
|
CaptureBackend::Dummy => write!(f, "dummy"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, ValueEnum)]
|
||||||
|
pub enum EmulationBackend {
|
||||||
|
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
|
||||||
|
Wlroots,
|
||||||
|
#[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
|
||||||
|
Libei,
|
||||||
|
#[cfg(all(unix, feature = "xdg_desktop_portal", not(target_os = "macos")))]
|
||||||
|
Xdp,
|
||||||
|
#[cfg(all(unix, feature = "x11", not(target_os = "macos")))]
|
||||||
|
X11,
|
||||||
|
#[cfg(windows)]
|
||||||
|
Windows,
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
MacOs,
|
||||||
|
Dummy,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for EmulationBackend {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
|
||||||
|
EmulationBackend::Wlroots => write!(f, "wlroots"),
|
||||||
|
#[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
|
||||||
|
EmulationBackend::Libei => write!(f, "libei"),
|
||||||
|
#[cfg(all(unix, feature = "xdg_desktop_portal", not(target_os = "macos")))]
|
||||||
|
EmulationBackend::Xdp => write!(f, "xdg-desktop-portal"),
|
||||||
|
#[cfg(all(unix, feature = "x11", not(target_os = "macos")))]
|
||||||
|
EmulationBackend::X11 => write!(f, "X11"),
|
||||||
|
#[cfg(windows)]
|
||||||
|
EmulationBackend::Windows => write!(f, "windows"),
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
EmulationBackend::MacOs => write!(f, "macos"),
|
||||||
|
EmulationBackend::Dummy => write!(f, "dummy"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, Serialize, ValueEnum)]
|
||||||
pub enum Frontend {
|
pub enum Frontend {
|
||||||
Gtk,
|
Gtk,
|
||||||
Cli,
|
Cli,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for Frontend {
|
||||||
|
fn default() -> Self {
|
||||||
|
if cfg!(feature = "gtk") {
|
||||||
|
Self::Gtk
|
||||||
|
} else {
|
||||||
|
Self::Cli
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
|
pub capture_backend: Option<CaptureBackend>,
|
||||||
|
pub emulation_backend: Option<EmulationBackend>,
|
||||||
pub frontend: Frontend,
|
pub frontend: Frontend,
|
||||||
pub port: u16,
|
pub port: u16,
|
||||||
pub clients: Vec<(TomlClient, Position)>,
|
pub clients: Vec<(TomlClient, Position)>,
|
||||||
@@ -129,33 +223,14 @@ impl Config {
|
|||||||
Ok(c) => Some(c),
|
Ok(c) => Some(c),
|
||||||
};
|
};
|
||||||
|
|
||||||
let frontend = match args.frontend {
|
let frontend_arg = args.frontend;
|
||||||
None => match &config_toml {
|
let frontend_cfg = config_toml.as_ref().and_then(|c| c.frontend);
|
||||||
Some(c) => c.frontend.clone(),
|
let frontend = frontend_arg.or(frontend_cfg).unwrap_or_default();
|
||||||
None => None,
|
|
||||||
},
|
|
||||||
frontend => frontend,
|
|
||||||
};
|
|
||||||
|
|
||||||
let frontend = match frontend {
|
let port = args
|
||||||
#[cfg(feature = "gtk")]
|
.port
|
||||||
None => Frontend::Gtk,
|
.or(config_toml.as_ref().and_then(|c| c.port))
|
||||||
#[cfg(not(feature = "gtk"))]
|
.unwrap_or(DEFAULT_PORT);
|
||||||
None => Frontend::Cli,
|
|
||||||
Some(s) => match s.as_str() {
|
|
||||||
"cli" => Frontend::Cli,
|
|
||||||
"gtk" => Frontend::Gtk,
|
|
||||||
_ => Frontend::Cli,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let port = match args.port {
|
|
||||||
Some(port) => port,
|
|
||||||
None => match &config_toml {
|
|
||||||
Some(c) => c.port.unwrap_or(DEFAULT_PORT),
|
|
||||||
None => DEFAULT_PORT,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
log::debug!("{config_toml:?}");
|
log::debug!("{config_toml:?}");
|
||||||
let release_bind = config_toml
|
let release_bind = config_toml
|
||||||
@@ -163,6 +238,14 @@ impl Config {
|
|||||||
.and_then(|c| c.release_bind.clone())
|
.and_then(|c| c.release_bind.clone())
|
||||||
.unwrap_or(Vec::from_iter(DEFAULT_RELEASE_KEYS.iter().cloned()));
|
.unwrap_or(Vec::from_iter(DEFAULT_RELEASE_KEYS.iter().cloned()));
|
||||||
|
|
||||||
|
let capture_backend = args
|
||||||
|
.capture_backend
|
||||||
|
.or(config_toml.as_ref().and_then(|c| c.capture_backend));
|
||||||
|
|
||||||
|
let emulation_backend = args
|
||||||
|
.emulation_backend
|
||||||
|
.or(config_toml.as_ref().and_then(|c| c.emulation_backend));
|
||||||
|
|
||||||
let mut clients: Vec<(TomlClient, Position)> = vec![];
|
let mut clients: Vec<(TomlClient, Position)> = vec![];
|
||||||
|
|
||||||
if let Some(config_toml) = config_toml {
|
if let Some(config_toml) = config_toml {
|
||||||
@@ -185,6 +268,8 @@ impl Config {
|
|||||||
let test_emulation = args.test_emulation;
|
let test_emulation = args.test_emulation;
|
||||||
|
|
||||||
Ok(Config {
|
Ok(Config {
|
||||||
|
capture_backend,
|
||||||
|
emulation_backend,
|
||||||
daemon,
|
daemon,
|
||||||
frontend,
|
frontend,
|
||||||
clients,
|
clients,
|
||||||
|
|||||||
112
src/emulate.rs
112
src/emulate.rs
@@ -3,10 +3,13 @@ use std::future;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
client::{ClientEvent, ClientHandle},
|
client::{ClientEvent, ClientHandle},
|
||||||
|
config::EmulationBackend,
|
||||||
event::Event,
|
event::Event,
|
||||||
};
|
};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
|
use self::error::EmulationCreationError;
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
pub mod windows;
|
pub mod windows;
|
||||||
|
|
||||||
@@ -27,6 +30,7 @@ pub mod macos;
|
|||||||
|
|
||||||
/// fallback input emulation (logs events)
|
/// fallback input emulation (logs events)
|
||||||
pub mod dummy;
|
pub mod dummy;
|
||||||
|
pub mod error;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait InputEmulation: Send {
|
pub trait InputEmulation: Send {
|
||||||
@@ -41,58 +45,60 @@ pub trait InputEmulation: Send {
|
|||||||
async fn destroy(&mut self);
|
async fn destroy(&mut self);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn create() -> Box<dyn InputEmulation> {
|
pub async fn create_backend(
|
||||||
#[cfg(windows)]
|
backend: EmulationBackend,
|
||||||
match windows::WindowsEmulation::new() {
|
) -> Result<Box<dyn InputEmulation>, EmulationCreationError> {
|
||||||
Ok(c) => return Box::new(c),
|
match backend {
|
||||||
Err(e) => log::warn!("windows input emulation unavailable: {e}"),
|
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
|
||||||
|
EmulationBackend::Wlroots => Ok(Box::new(wlroots::WlrootsEmulation::new()?)),
|
||||||
|
#[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
|
||||||
|
EmulationBackend::Libei => Ok(Box::new(libei::LibeiEmulation::new().await?)),
|
||||||
|
#[cfg(all(unix, feature = "x11", not(target_os = "macos")))]
|
||||||
|
EmulationBackend::X11 => Ok(Box::new(x11::X11Emulation::new()?)),
|
||||||
|
#[cfg(all(unix, feature = "xdg_desktop_portal", not(target_os = "macos")))]
|
||||||
|
EmulationBackend::Xdp => Ok(Box::new(
|
||||||
|
xdg_desktop_portal::DesktopPortalEmulation::new().await?,
|
||||||
|
)),
|
||||||
|
#[cfg(windows)]
|
||||||
|
EmulationBackend::Windows => Ok(Box::new(windows::WindowsEmulation::new()?)),
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
EmulationBackend::MacOs => Ok(Box::new(macos::MacOSEmulation::new()?)),
|
||||||
|
EmulationBackend::Dummy => Ok(Box::new(dummy::DummyEmulation::new())),
|
||||||
}
|
}
|
||||||
|
}
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
match macos::MacOSEmulation::new() {
|
pub async fn create(
|
||||||
Ok(c) => {
|
backend: Option<EmulationBackend>,
|
||||||
log::info!("using macos input emulation");
|
) -> Result<Box<dyn InputEmulation>, EmulationCreationError> {
|
||||||
return Box::new(c);
|
if let Some(backend) = backend {
|
||||||
}
|
let b = create_backend(backend).await;
|
||||||
Err(e) => log::error!("macos input emulatino not available: {e}"),
|
if b.is_ok() {
|
||||||
}
|
log::info!("using emulation backend: {backend}");
|
||||||
|
}
|
||||||
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
|
return b;
|
||||||
match wlroots::WlrootsEmulation::new() {
|
}
|
||||||
Ok(c) => {
|
|
||||||
log::info!("using wlroots input emulation");
|
for backend in [
|
||||||
return Box::new(c);
|
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
|
||||||
}
|
EmulationBackend::Wlroots,
|
||||||
Err(e) => log::info!("wayland backend not available: {e}"),
|
#[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
|
||||||
}
|
EmulationBackend::Libei,
|
||||||
|
#[cfg(all(unix, feature = "x11", not(target_os = "macos")))]
|
||||||
#[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
|
EmulationBackend::X11,
|
||||||
match libei::LibeiEmulation::new().await {
|
#[cfg(windows)]
|
||||||
Ok(c) => {
|
EmulationBackend::Windows,
|
||||||
log::info!("using libei input emulation");
|
#[cfg(target_os = "macos")]
|
||||||
return Box::new(c);
|
EmulationBackend::MacOs,
|
||||||
}
|
EmulationBackend::Dummy,
|
||||||
Err(e) => log::info!("libei not available: {e}"),
|
] {
|
||||||
}
|
match create_backend(backend).await {
|
||||||
|
Ok(b) => {
|
||||||
#[cfg(all(unix, feature = "xdg_desktop_portal", not(target_os = "macos")))]
|
log::info!("using emulation backend: {backend}");
|
||||||
match xdg_desktop_portal::DesktopPortalEmulation::new().await {
|
return Ok(b);
|
||||||
Ok(c) => {
|
}
|
||||||
log::info!("using xdg-remote-desktop-portal input emulation");
|
Err(e) => log::warn!("{e}"),
|
||||||
return Box::new(c);
|
}
|
||||||
}
|
}
|
||||||
Err(e) => log::info!("remote desktop portal not available: {e}"),
|
|
||||||
}
|
Err(EmulationCreationError::NoAvailableBackend)
|
||||||
|
|
||||||
#[cfg(all(unix, feature = "x11", not(target_os = "macos")))]
|
|
||||||
match x11::X11Emulation::new() {
|
|
||||||
Ok(c) => {
|
|
||||||
log::info!("using x11 input emulation");
|
|
||||||
return Box::new(c);
|
|
||||||
}
|
|
||||||
Err(e) => log::info!("x11 input emulation not available: {e}"),
|
|
||||||
}
|
|
||||||
|
|
||||||
log::error!("falling back to dummy input emulation");
|
|
||||||
Box::new(dummy::DummyEmulation::new())
|
|
||||||
}
|
}
|
||||||
|
|||||||
168
src/emulate/error.rs
Normal file
168
src/emulate/error.rs
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
use thiserror::Error;
|
||||||
|
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
|
||||||
|
use wayland_client::{
|
||||||
|
backend::WaylandError,
|
||||||
|
globals::{BindError, GlobalError},
|
||||||
|
ConnectError, DispatchError,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum EmulationCreationError {
|
||||||
|
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
|
||||||
|
Wlroots(#[from] WlrootsEmulationCreationError),
|
||||||
|
#[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
|
||||||
|
Libei(#[from] LibeiEmulationCreationError),
|
||||||
|
#[cfg(all(unix, feature = "xdg_desktop_portal", not(target_os = "macos")))]
|
||||||
|
Xdp(#[from] XdpEmulationCreationError),
|
||||||
|
#[cfg(all(unix, feature = "x11", not(target_os = "macos")))]
|
||||||
|
X11(#[from] X11EmulationCreationError),
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
MacOs(#[from] MacOSEmulationCreationError),
|
||||||
|
#[cfg(windows)]
|
||||||
|
Windows(#[from] WindowsEmulationCreationError),
|
||||||
|
NoAvailableBackend,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for EmulationCreationError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let reason = match self {
|
||||||
|
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
|
||||||
|
EmulationCreationError::Wlroots(e) => format!("wlroots backend: {e}"),
|
||||||
|
#[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
|
||||||
|
EmulationCreationError::Libei(e) => format!("libei backend: {e}"),
|
||||||
|
#[cfg(all(unix, feature = "xdg_desktop_portal", not(target_os = "macos")))]
|
||||||
|
EmulationCreationError::Xdp(e) => format!("desktop portal backend: {e}"),
|
||||||
|
#[cfg(all(unix, feature = "x11", not(target_os = "macos")))]
|
||||||
|
EmulationCreationError::X11(e) => format!("x11 backend: {e}"),
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
EmulationCreationError::MacOs(e) => format!("macos backend: {e}"),
|
||||||
|
#[cfg(windows)]
|
||||||
|
EmulationCreationError::Windows(e) => format!("windows backend: {e}"),
|
||||||
|
EmulationCreationError::NoAvailableBackend => "no backend available".to_string(),
|
||||||
|
};
|
||||||
|
write!(f, "could not create input emulation backend: {reason}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum WlrootsEmulationCreationError {
|
||||||
|
Connect(#[from] ConnectError),
|
||||||
|
Global(#[from] GlobalError),
|
||||||
|
Wayland(#[from] WaylandError),
|
||||||
|
Bind(#[from] WaylandBindError),
|
||||||
|
Dispatch(#[from] DispatchError),
|
||||||
|
Io(#[from] std::io::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub struct WaylandBindError {
|
||||||
|
inner: BindError,
|
||||||
|
protocol: &'static str,
|
||||||
|
}
|
||||||
|
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
|
||||||
|
impl WaylandBindError {
|
||||||
|
pub(crate) fn new(inner: BindError, protocol: &'static str) -> Self {
|
||||||
|
Self { inner, protocol }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
|
||||||
|
impl Display for WaylandBindError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{} protocol not supported: {}",
|
||||||
|
self.protocol, self.inner
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
|
||||||
|
impl Display for WlrootsEmulationCreationError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
WlrootsEmulationCreationError::Bind(e) => write!(f, "{e}"),
|
||||||
|
WlrootsEmulationCreationError::Connect(e) => {
|
||||||
|
write!(f, "could not connect to wayland compositor: {e}")
|
||||||
|
}
|
||||||
|
WlrootsEmulationCreationError::Global(e) => write!(f, "wayland error: {e}"),
|
||||||
|
WlrootsEmulationCreationError::Wayland(e) => write!(f, "wayland error: {e}"),
|
||||||
|
WlrootsEmulationCreationError::Dispatch(e) => {
|
||||||
|
write!(f, "error dispatching wayland events: {e}")
|
||||||
|
}
|
||||||
|
WlrootsEmulationCreationError::Io(e) => write!(f, "io error: {e}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum LibeiEmulationCreationError {
|
||||||
|
Ashpd(#[from] ashpd::Error),
|
||||||
|
Io(#[from] std::io::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
|
||||||
|
impl Display for LibeiEmulationCreationError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
LibeiEmulationCreationError::Ashpd(e) => write!(f, "xdg-desktop-portal: {e}"),
|
||||||
|
LibeiEmulationCreationError::Io(e) => write!(f, "io error: {e}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(unix, feature = "xdg_desktop_portal", not(target_os = "macos")))]
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum XdpEmulationCreationError {
|
||||||
|
Ashpd(#[from] ashpd::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(unix, feature = "xdg_desktop_portal", not(target_os = "macos")))]
|
||||||
|
impl Display for XdpEmulationCreationError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
XdpEmulationCreationError::Ashpd(e) => write!(f, "portal error: {e}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(unix, feature = "x11", not(target_os = "macos")))]
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum X11EmulationCreationError {
|
||||||
|
OpenDisplay,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(unix, feature = "x11", not(target_os = "macos")))]
|
||||||
|
impl Display for X11EmulationCreationError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
X11EmulationCreationError::OpenDisplay => write!(f, "could not open display!"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum MacOSEmulationCreationError {
|
||||||
|
EventSourceCreation,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
impl Display for MacOSEmulationCreationError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
MacOSEmulationCreationError::EventSourceCreation => {
|
||||||
|
write!(f, "could not create event source")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum WindowsEmulationCreationError {}
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
|
use anyhow::{anyhow, Result};
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
|
io,
|
||||||
os::{fd::OwnedFd, unix::net::UnixStream},
|
os::{fd::OwnedFd, unix::net::UnixStream},
|
||||||
time::{SystemTime, UNIX_EPOCH},
|
time::{SystemTime, UNIX_EPOCH},
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
|
||||||
use ashpd::{
|
use ashpd::{
|
||||||
desktop::{
|
desktop::{
|
||||||
remote_desktop::{DeviceType, RemoteDesktop},
|
remote_desktop::{DeviceType, RemoteDesktop},
|
||||||
@@ -27,6 +28,8 @@ use crate::{
|
|||||||
event::Event,
|
event::Event,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use super::error::LibeiEmulationCreationError;
|
||||||
|
|
||||||
pub struct LibeiEmulation {
|
pub struct LibeiEmulation {
|
||||||
handshake: bool,
|
handshake: bool,
|
||||||
context: ei::Context,
|
context: ei::Context,
|
||||||
@@ -77,14 +80,14 @@ async fn get_ei_fd() -> Result<OwnedFd, ashpd::Error> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl LibeiEmulation {
|
impl LibeiEmulation {
|
||||||
pub async fn new() -> Result<Self> {
|
pub async fn new() -> Result<Self, LibeiEmulationCreationError> {
|
||||||
// fd is owned by the message, so we need to dup it
|
// fd is owned by the message, so we need to dup it
|
||||||
let eifd = get_ei_fd().await?;
|
let eifd = get_ei_fd().await?;
|
||||||
let stream = UnixStream::from(eifd);
|
let stream = UnixStream::from(eifd);
|
||||||
// let stream = UnixStream::connect("/run/user/1000/eis-0")?;
|
// let stream = UnixStream::connect("/run/user/1000/eis-0")?;
|
||||||
stream.set_nonblocking(true)?;
|
stream.set_nonblocking(true)?;
|
||||||
let context = ei::Context::new(stream)?;
|
let context = ei::Context::new(stream)?;
|
||||||
context.flush()?;
|
context.flush().map_err(|e| io::Error::new(e.kind(), e))?;
|
||||||
let events = EiEventStream::new(context.clone())?;
|
let events = EiEventStream::new(context.clone())?;
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
handshake: false,
|
handshake: false,
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
use crate::client::{ClientEvent, ClientHandle};
|
use crate::client::{ClientEvent, ClientHandle};
|
||||||
use crate::emulate::InputEmulation;
|
use crate::emulate::InputEmulation;
|
||||||
use crate::event::{Event, KeyboardEvent, PointerEvent};
|
use crate::event::{Event, KeyboardEvent, PointerEvent};
|
||||||
use anyhow::{anyhow, Result};
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use core_graphics::display::{CGDisplayBounds, CGMainDisplayID, CGPoint};
|
use core_graphics::display::{CGDisplayBounds, CGMainDisplayID, CGPoint};
|
||||||
use core_graphics::event::{
|
use core_graphics::event::{
|
||||||
@@ -13,6 +12,8 @@ use std::ops::{Index, IndexMut};
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use tokio::task::AbortHandle;
|
use tokio::task::AbortHandle;
|
||||||
|
|
||||||
|
use super::error::MacOSEmulationCreationError;
|
||||||
|
|
||||||
const DEFAULT_REPEAT_DELAY: Duration = Duration::from_millis(500);
|
const DEFAULT_REPEAT_DELAY: Duration = Duration::from_millis(500);
|
||||||
const DEFAULT_REPEAT_INTERVAL: Duration = Duration::from_millis(32);
|
const DEFAULT_REPEAT_INTERVAL: Duration = Duration::from_millis(32);
|
||||||
|
|
||||||
@@ -53,11 +54,9 @@ impl IndexMut<CGMouseButton> for ButtonState {
|
|||||||
unsafe impl Send for MacOSEmulation {}
|
unsafe impl Send for MacOSEmulation {}
|
||||||
|
|
||||||
impl MacOSEmulation {
|
impl MacOSEmulation {
|
||||||
pub fn new() -> Result<Self> {
|
pub fn new() -> Result<Self, MacOSEmulationCreationError> {
|
||||||
let event_source = match CGEventSource::new(CGEventSourceStateID::CombinedSessionState) {
|
let event_source = CGEventSource::new(CGEventSourceStateID::CombinedSessionState)
|
||||||
Ok(e) => e,
|
.map_err(|_| MacOSEmulationCreationError::EventSourceCreation)?;
|
||||||
Err(_) => return Err(anyhow!("event source creation failed!")),
|
|
||||||
};
|
|
||||||
let button_state = ButtonState {
|
let button_state = ButtonState {
|
||||||
left: false,
|
left: false,
|
||||||
right: false,
|
right: false,
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
|
use super::error::WindowsEmulationCreationError;
|
||||||
use crate::{
|
use crate::{
|
||||||
emulate::InputEmulation,
|
emulate::InputEmulation,
|
||||||
event::{KeyboardEvent, PointerEvent},
|
event::{KeyboardEvent, PointerEvent},
|
||||||
scancode,
|
scancode,
|
||||||
};
|
};
|
||||||
use anyhow::Result;
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use std::ops::BitOrAssign;
|
use std::ops::BitOrAssign;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
@@ -33,7 +33,7 @@ pub struct WindowsEmulation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl WindowsEmulation {
|
impl WindowsEmulation {
|
||||||
pub fn new() -> Result<Self> {
|
pub fn new() -> Result<Self, WindowsEmulationCreationError> {
|
||||||
Ok(Self { repeat_task: None })
|
Ok(Self { repeat_task: None })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::client::{ClientEvent, ClientHandle};
|
use crate::client::{ClientEvent, ClientHandle};
|
||||||
use crate::emulate::InputEmulation;
|
use crate::emulate::{error::WlrootsEmulationCreationError, InputEmulation};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::io;
|
use std::io;
|
||||||
@@ -7,7 +7,6 @@ use std::os::fd::{AsFd, OwnedFd};
|
|||||||
use wayland_client::backend::WaylandError;
|
use wayland_client::backend::WaylandError;
|
||||||
use wayland_client::WEnum;
|
use wayland_client::WEnum;
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
|
||||||
use wayland_client::protocol::wl_keyboard::{self, WlKeyboard};
|
use wayland_client::protocol::wl_keyboard::{self, WlKeyboard};
|
||||||
use wayland_client::protocol::wl_pointer::{Axis, ButtonState};
|
use wayland_client::protocol::wl_pointer::{Axis, ButtonState};
|
||||||
use wayland_client::protocol::wl_seat::WlSeat;
|
use wayland_client::protocol::wl_seat::WlSeat;
|
||||||
@@ -30,6 +29,8 @@ use wayland_client::{
|
|||||||
|
|
||||||
use crate::event::{Event, KeyboardEvent, PointerEvent};
|
use crate::event::{Event, KeyboardEvent, PointerEvent};
|
||||||
|
|
||||||
|
use super::error::WaylandBindError;
|
||||||
|
|
||||||
struct State {
|
struct State {
|
||||||
keymap: Option<(u32, OwnedFd, u32)>,
|
keymap: Option<(u32, OwnedFd, u32)>,
|
||||||
input_for_client: HashMap<ClientHandle, VirtualInput>,
|
input_for_client: HashMap<ClientHandle, VirtualInput>,
|
||||||
@@ -47,18 +48,21 @@ pub(crate) struct WlrootsEmulation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl WlrootsEmulation {
|
impl WlrootsEmulation {
|
||||||
pub fn new() -> Result<Self> {
|
pub fn new() -> Result<Self, WlrootsEmulationCreationError> {
|
||||||
let conn = Connection::connect_to_env()?;
|
let conn = Connection::connect_to_env()?;
|
||||||
let (globals, queue) = registry_queue_init::<State>(&conn)?;
|
let (globals, queue) = registry_queue_init::<State>(&conn)?;
|
||||||
let qh = queue.handle();
|
let qh = queue.handle();
|
||||||
|
|
||||||
let seat: wl_seat::WlSeat = match globals.bind(&qh, 7..=8, ()) {
|
let seat: wl_seat::WlSeat = globals
|
||||||
Ok(wl_seat) => wl_seat,
|
.bind(&qh, 7..=8, ())
|
||||||
Err(_) => return Err(anyhow!("wl_seat >= v7 not supported")),
|
.map_err(|e| WaylandBindError::new(e, "wl_seat 7..=8"))?;
|
||||||
};
|
|
||||||
|
|
||||||
let vpm: VpManager = globals.bind(&qh, 1..=1, ())?;
|
let vpm: VpManager = globals
|
||||||
let vkm: VkManager = globals.bind(&qh, 1..=1, ())?;
|
.bind(&qh, 1..=1, ())
|
||||||
|
.map_err(|e| WaylandBindError::new(e, "wlr-virtual-pointer-unstable-v1"))?;
|
||||||
|
let vkm: VkManager = globals
|
||||||
|
.bind(&qh, 1..=1, ())
|
||||||
|
.map_err(|e| WaylandBindError::new(e, "virtual-keyboard-unstable-v1"))?;
|
||||||
|
|
||||||
let input_for_client: HashMap<ClientHandle, VirtualInput> = HashMap::new();
|
let input_for_client: HashMap<ClientHandle, VirtualInput> = HashMap::new();
|
||||||
|
|
||||||
@@ -75,7 +79,7 @@ impl WlrootsEmulation {
|
|||||||
queue,
|
queue,
|
||||||
};
|
};
|
||||||
while emulate.state.keymap.is_none() {
|
while emulate.state.keymap.is_none() {
|
||||||
emulate.queue.blocking_dispatch(&mut emulate.state).unwrap();
|
emulate.queue.blocking_dispatch(&mut emulate.state)?;
|
||||||
}
|
}
|
||||||
// let fd = unsafe { &File::from_raw_fd(emulate.state.keymap.unwrap().1.as_raw_fd()) };
|
// let fd = unsafe { &File::from_raw_fd(emulate.state.keymap.unwrap().1.as_raw_fd()) };
|
||||||
// let mmap = unsafe { MmapOptions::new().map_copy(fd).unwrap() };
|
// let mmap = unsafe { MmapOptions::new().map_copy(fd).unwrap() };
|
||||||
@@ -182,7 +186,8 @@ impl VirtualInput {
|
|||||||
}
|
}
|
||||||
PointerEvent::AxisDiscrete120 { axis, value } => {
|
PointerEvent::AxisDiscrete120 { axis, value } => {
|
||||||
let axis: Axis = (axis as u32).try_into()?;
|
let axis: Axis = (axis as u32).try_into()?;
|
||||||
self.pointer.axis(0, axis, (value / 15) as f64);
|
self.pointer
|
||||||
|
.axis_discrete(0, axis, value as f64 / 6., value / 120);
|
||||||
self.pointer.frame();
|
self.pointer.frame();
|
||||||
}
|
}
|
||||||
PointerEvent::Frame {} => self.pointer.frame(),
|
PointerEvent::Frame {} => self.pointer.frame(),
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
use anyhow::{anyhow, Result};
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
use x11::{
|
use x11::{
|
||||||
@@ -14,6 +13,8 @@ use crate::{
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use super::error::X11EmulationCreationError;
|
||||||
|
|
||||||
pub struct X11Emulation {
|
pub struct X11Emulation {
|
||||||
display: *mut xlib::Display,
|
display: *mut xlib::Display,
|
||||||
}
|
}
|
||||||
@@ -21,11 +22,11 @@ pub struct X11Emulation {
|
|||||||
unsafe impl Send for X11Emulation {}
|
unsafe impl Send for X11Emulation {}
|
||||||
|
|
||||||
impl X11Emulation {
|
impl X11Emulation {
|
||||||
pub fn new() -> Result<Self> {
|
pub fn new() -> Result<Self, X11EmulationCreationError> {
|
||||||
let display = unsafe {
|
let display = unsafe {
|
||||||
match xlib::XOpenDisplay(ptr::null()) {
|
match xlib::XOpenDisplay(ptr::null()) {
|
||||||
d if d == ptr::null::<xlib::Display>() as *mut xlib::Display => {
|
d if d == ptr::null::<xlib::Display>() as *mut xlib::Display => {
|
||||||
Err(anyhow!("could not open display"))
|
Err(X11EmulationCreationError::OpenDisplay)
|
||||||
}
|
}
|
||||||
display => Ok(display),
|
display => Ok(display),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,13 +17,15 @@ use crate::{
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use super::error::XdpEmulationCreationError;
|
||||||
|
|
||||||
pub struct DesktopPortalEmulation<'a> {
|
pub struct DesktopPortalEmulation<'a> {
|
||||||
proxy: RemoteDesktop<'a>,
|
proxy: RemoteDesktop<'a>,
|
||||||
session: Session<'a>,
|
session: Session<'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> DesktopPortalEmulation<'a> {
|
impl<'a> DesktopPortalEmulation<'a> {
|
||||||
pub async fn new() -> Result<DesktopPortalEmulation<'a>> {
|
pub async fn new() -> Result<DesktopPortalEmulation<'a>, XdpEmulationCreationError> {
|
||||||
log::debug!("connecting to org.freedesktop.portal.RemoteDesktop portal ...");
|
log::debug!("connecting to org.freedesktop.portal.RemoteDesktop portal ...");
|
||||||
let proxy = RemoteDesktop::new().await?;
|
let proxy = RemoteDesktop::new().await?;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use crate::client::{ClientEvent, Position};
|
use crate::client::{ClientEvent, Position};
|
||||||
|
use crate::config::Config;
|
||||||
use crate::emulate;
|
use crate::emulate;
|
||||||
use crate::event::{Event, PointerEvent};
|
use crate::event::{Event, PointerEvent};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
@@ -13,14 +14,16 @@ pub fn run() -> Result<()> {
|
|||||||
.enable_time()
|
.enable_time()
|
||||||
.build()?;
|
.build()?;
|
||||||
|
|
||||||
runtime.block_on(LocalSet::new().run_until(input_emulation_test()))
|
let config = Config::new()?;
|
||||||
|
|
||||||
|
runtime.block_on(LocalSet::new().run_until(input_emulation_test(config)))
|
||||||
}
|
}
|
||||||
|
|
||||||
const FREQUENCY_HZ: f64 = 1.0;
|
const FREQUENCY_HZ: f64 = 1.0;
|
||||||
const RADIUS: f64 = 100.0;
|
const RADIUS: f64 = 100.0;
|
||||||
|
|
||||||
async fn input_emulation_test() -> Result<()> {
|
async fn input_emulation_test(config: Config) -> Result<()> {
|
||||||
let mut emulation = emulate::create().await;
|
let mut emulation = emulate::create(config.emulation_backend).await?;
|
||||||
emulation
|
emulation
|
||||||
.notify(ClientEvent::Create(0, Position::Left))
|
.notify(ClientEvent::Create(0, Position::Left))
|
||||||
.await;
|
.await;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ use std::{
|
|||||||
use crate::frontend::{gtk::window::Window, FrontendRequest};
|
use crate::frontend::{gtk::window::Window, FrontendRequest};
|
||||||
|
|
||||||
use adw::Application;
|
use adw::Application;
|
||||||
|
use endi::{Endian, ReadBytes};
|
||||||
use gtk::{
|
use gtk::{
|
||||||
gdk::Display, glib::clone, prelude::*, subclass::prelude::ObjectSubclassIsExt, IconTheme,
|
gdk::Display, glib::clone, prelude::*, subclass::prelude::ObjectSubclassIsExt, IconTheme,
|
||||||
};
|
};
|
||||||
@@ -85,16 +86,14 @@ fn build_ui(app: &Application) {
|
|||||||
gio::spawn_blocking(move || {
|
gio::spawn_blocking(move || {
|
||||||
match loop {
|
match loop {
|
||||||
// read length
|
// read length
|
||||||
let mut len = [0u8; 8];
|
let len = match rx.read_u64(Endian::Big) {
|
||||||
match rx.read_exact(&mut len) {
|
Ok(l) => l,
|
||||||
Ok(_) => (),
|
|
||||||
Err(e) if e.kind() == ErrorKind::UnexpectedEof => break Ok(()),
|
Err(e) if e.kind() == ErrorKind::UnexpectedEof => break Ok(()),
|
||||||
Err(e) => break Err(e),
|
Err(e) => break Err(e),
|
||||||
};
|
};
|
||||||
let len = usize::from_be_bytes(len);
|
|
||||||
|
|
||||||
// read payload
|
// read payload
|
||||||
let mut buf = vec![0u8; len];
|
let mut buf = vec![0u8; len as usize];
|
||||||
match rx.read_exact(&mut buf) {
|
match rx.read_exact(&mut buf) {
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
Err(e) if e.kind() == ErrorKind::UnexpectedEof => break Ok(()),
|
Err(e) if e.kind() == ErrorKind::UnexpectedEof => break Ok(()),
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ use std::net::TcpStream;
|
|||||||
|
|
||||||
use adw::prelude::*;
|
use adw::prelude::*;
|
||||||
use adw::subclass::prelude::*;
|
use adw::subclass::prelude::*;
|
||||||
|
use endi::{Endian, WriteBytes};
|
||||||
use glib::{clone, Object};
|
use glib::{clone, Object};
|
||||||
use gtk::{
|
use gtk::{
|
||||||
gio,
|
gio,
|
||||||
@@ -265,8 +266,7 @@ impl Window {
|
|||||||
let mut stream = self.imp().stream.borrow_mut();
|
let mut stream = self.imp().stream.borrow_mut();
|
||||||
let stream = stream.as_mut().unwrap();
|
let stream = stream.as_mut().unwrap();
|
||||||
let bytes = json.as_bytes();
|
let bytes = json.as_bytes();
|
||||||
let len = bytes.len().to_be_bytes();
|
if let Err(e) = stream.write_u64(Endian::Big, bytes.len() as u64) {
|
||||||
if let Err(e) = stream.write(&len) {
|
|
||||||
log::error!("error sending message: {e}");
|
log::error!("error sending message: {e}");
|
||||||
};
|
};
|
||||||
if let Err(e) = stream.write(bytes) {
|
if let Err(e) = stream.write(bytes) {
|
||||||
|
|||||||
@@ -68,10 +68,12 @@ fn run_service(config: &Config) -> Result<()> {
|
|||||||
// run async event loop
|
// run async event loop
|
||||||
runtime.block_on(LocalSet::new().run_until(async {
|
runtime.block_on(LocalSet::new().run_until(async {
|
||||||
// run main loop
|
// run main loop
|
||||||
log::info!("Press Ctrl+Alt+Shift+Super to release the mouse");
|
log::info!("Press {:?} to release the mouse", config.release_bind);
|
||||||
|
|
||||||
let server = Server::new(config);
|
let server = Server::new(config);
|
||||||
server.run().await?;
|
server
|
||||||
|
.run(config.capture_backend, config.emulation_backend)
|
||||||
|
.await?;
|
||||||
|
|
||||||
log::debug!("service exiting");
|
log::debug!("service exiting");
|
||||||
anyhow::Ok(())
|
anyhow::Ok(())
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ use serde::{Deserialize, Serialize};
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* https://learn.microsoft.com/en-us/windows/win32/inputdev/about-keyboard-input
|
* https://learn.microsoft.com/en-us/windows/win32/inputdev/about-keyboard-input
|
||||||
|
* https://download.microsoft.com/download/1/6/1/161ba512-40e2-4cc9-843a-923143f3456c/translate.pdf
|
||||||
|
* https://kbd-project.org/docs/scancodes/scancodes-1.html
|
||||||
*/
|
*/
|
||||||
#[repr(u32)]
|
#[repr(u32)]
|
||||||
#[derive(Debug, Clone, Copy, TryFromPrimitive)]
|
#[derive(Debug, Clone, Copy, TryFromPrimitive)]
|
||||||
@@ -120,15 +122,15 @@ pub enum Windows {
|
|||||||
KeyF21 = 0x006C,
|
KeyF21 = 0x006C,
|
||||||
KeyF22 = 0x006D,
|
KeyF22 = 0x006D,
|
||||||
KeyF23 = 0x006E,
|
KeyF23 = 0x006E,
|
||||||
KeyF24 = 0x0076,
|
KeyF24 = 0x0076, // KeyLANG5
|
||||||
KeypadComma = 0x007E,
|
KeypadComma = 0x007E,
|
||||||
KeyInternational1 = 0x0073,
|
KeyInternational1 = 0x0073,
|
||||||
KeyInternational2 = 0x0070,
|
KeyInternational2 = 0x0070,
|
||||||
KeyInternational3 = 0x007D,
|
KeyInternational3 = 0x007D, // typo in doc -> its Int'l 3 not Int'l 2
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
KeyInternational4 = 0x0079, // FIXME unused
|
KeyInternational4 = 0x0079,
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
KeyInternational5 = 0x007B, // FIXME unused
|
KeyInternational5 = 0x007B,
|
||||||
// KeyInternational6 = 0x005C,
|
// KeyInternational6 = 0x005C,
|
||||||
KeyLANG1 = 0x0072,
|
KeyLANG1 = 0x0072,
|
||||||
KeyLANG2 = 0x0071,
|
KeyLANG2 = 0x0071,
|
||||||
@@ -141,6 +143,7 @@ pub enum Windows {
|
|||||||
KeyLeftGUI = 0xE05B,
|
KeyLeftGUI = 0xE05B,
|
||||||
KeyRightCtrl = 0xE01D,
|
KeyRightCtrl = 0xE01D,
|
||||||
KeyRightShift = 0x0036,
|
KeyRightShift = 0x0036,
|
||||||
|
KeyFakeRightShift = 0xE036,
|
||||||
KeyRightAlt = 0xE038,
|
KeyRightAlt = 0xE038,
|
||||||
KeyRightGUI = 0xE05C,
|
KeyRightGUI = 0xE05C,
|
||||||
KeyScanNextTrack = 0xE019,
|
KeyScanNextTrack = 0xE019,
|
||||||
@@ -293,7 +296,7 @@ pub enum Linux {
|
|||||||
KeyPause = 119,
|
KeyPause = 119,
|
||||||
KeyScale = 120, /* AL Compiz Scale (Expose) */
|
KeyScale = 120, /* AL Compiz Scale (Expose) */
|
||||||
KeyKpcomma = 121,
|
KeyKpcomma = 121,
|
||||||
KeyHangeul = 122,
|
KeyHanguel = 122,
|
||||||
// KEY_HANGUEL = KeyHangeul,
|
// KEY_HANGUEL = KeyHangeul,
|
||||||
KeyHanja = 123,
|
KeyHanja = 123,
|
||||||
KeyYen = 124,
|
KeyYen = 124,
|
||||||
@@ -518,16 +521,16 @@ impl TryFrom<Linux> for Windows {
|
|||||||
Linux::KeyKp3 => Ok(Self::Keypad3PageDn),
|
Linux::KeyKp3 => Ok(Self::Keypad3PageDn),
|
||||||
Linux::KeyKp0 => Ok(Self::Keypad0Insert),
|
Linux::KeyKp0 => Ok(Self::Keypad0Insert),
|
||||||
Linux::KeyKpDot => Ok(Self::KeypadDot),
|
Linux::KeyKpDot => Ok(Self::KeypadDot),
|
||||||
Linux::KeyZenkakuhankaku => Ok(Self::KeyLANG1), // TODO unsure
|
Linux::KeyZenkakuhankaku => Ok(Self::KeyF24), // KeyLANG5
|
||||||
Linux::Key102nd => Ok(Self::KeyNonUSSlashBar), // TODO unsure
|
Linux::Key102nd => Ok(Self::KeyNonUSSlashBar), // TODO unsure
|
||||||
Linux::KeyF11 => Ok(Self::KeyF11),
|
Linux::KeyF11 => Ok(Self::KeyF11),
|
||||||
Linux::KeyF12 => Ok(Self::KeyF12),
|
Linux::KeyF12 => Ok(Self::KeyF12),
|
||||||
Linux::KeyRo => Ok(Self::ErrorRollOver), // TODO unsure
|
Linux::KeyRo => Ok(Self::KeyInternational1),
|
||||||
Linux::KeyKatakana => Ok(Self::KeyLANG1), // TODO unsure
|
Linux::KeyKatakana => Ok(Self::KeyLANG3),
|
||||||
Linux::KeyHiragana => Ok(Self::KeyLANG2), // TODO unsure
|
Linux::KeyHiragana => Ok(Self::KeyLANG4),
|
||||||
Linux::KeyHenkan => Ok(Self::KeyLANG3), // TODO unsure
|
Linux::KeyHenkan => Ok(Self::KeyInternational4),
|
||||||
Linux::KeyKatakanahiragana => Ok(Self::KeyLANG4), // TODO unsure
|
Linux::KeyKatakanahiragana => Ok(Self::KeyInternational2),
|
||||||
Linux::KeyMuhenkan => Ok(Self::KeyLANG4), // TODO unsure
|
Linux::KeyMuhenkan => Ok(Self::KeyInternational5),
|
||||||
Linux::KeyKpJpComma => Ok(Self::KeypadComma),
|
Linux::KeyKpJpComma => Ok(Self::KeypadComma),
|
||||||
Linux::KeyKpEnter => Ok(Self::KeypadEnter),
|
Linux::KeyKpEnter => Ok(Self::KeypadEnter),
|
||||||
Linux::KeyRightCtrl => Ok(Self::KeyRightCtrl),
|
Linux::KeyRightCtrl => Ok(Self::KeyRightCtrl),
|
||||||
@@ -555,9 +558,9 @@ impl TryFrom<Linux> for Windows {
|
|||||||
Linux::KeyPause => Ok(Self::KeyPause),
|
Linux::KeyPause => Ok(Self::KeyPause),
|
||||||
Linux::KeyScale => Err(()), // TODO
|
Linux::KeyScale => Err(()), // TODO
|
||||||
Linux::KeyKpcomma => Ok(Self::KeypadComma),
|
Linux::KeyKpcomma => Ok(Self::KeypadComma),
|
||||||
Linux::KeyHangeul => Ok(Self::KeyInternational1), // TODO unsure
|
Linux::KeyHanguel => Ok(Self::KeyLANG1), // FIXME should be 00F2?
|
||||||
Linux::KeyHanja => Ok(Self::KeyInternational2), // TODO unsure
|
Linux::KeyHanja => Ok(Self::KeyLANG2), // FIXME should be 00F1?
|
||||||
Linux::KeyYen => Ok(Self::KeyInternational3), // TODO unsure
|
Linux::KeyYen => Ok(Self::KeyInternational3),
|
||||||
Linux::KeyLeftMeta => Ok(Self::KeyLeftGUI),
|
Linux::KeyLeftMeta => Ok(Self::KeyLeftGUI),
|
||||||
Linux::KeyRightmeta => Ok(Self::KeyRightGUI),
|
Linux::KeyRightmeta => Ok(Self::KeyRightGUI),
|
||||||
Linux::KeyCompose => Ok(Self::KeyApplication),
|
Linux::KeyCompose => Ok(Self::KeyApplication),
|
||||||
@@ -807,21 +810,22 @@ impl TryFrom<Windows> for Linux {
|
|||||||
Windows::KeyF23 => Ok(Self::KeyF23),
|
Windows::KeyF23 => Ok(Self::KeyF23),
|
||||||
Windows::KeyF24 => Ok(Self::KeyF24),
|
Windows::KeyF24 => Ok(Self::KeyF24),
|
||||||
Windows::KeypadComma => Ok(Self::KeyKpcomma),
|
Windows::KeypadComma => Ok(Self::KeyKpcomma),
|
||||||
Windows::KeyInternational1 => Ok(Self::KeyHangeul),
|
Windows::KeyInternational1 => Ok(Self::KeyRo),
|
||||||
Windows::KeyInternational2 => Ok(Self::KeyHanja),
|
Windows::KeyInternational2 => Ok(Self::KeyKatakanahiragana),
|
||||||
Windows::KeyInternational3 => Ok(Self::KeyYen),
|
Windows::KeyInternational3 => Ok(Self::KeyYen),
|
||||||
Windows::KeyInternational4 => Err(()),
|
Windows::KeyInternational4 => Ok(Self::KeyHenkan),
|
||||||
Windows::KeyInternational5 => Err(()),
|
Windows::KeyInternational5 => Ok(Self::KeyMuhenkan),
|
||||||
Windows::KeyLANG1 => Ok(Self::KeyKatakana),
|
Windows::KeyLANG1 => Ok(Self::KeyHanguel),
|
||||||
Windows::KeyLANG2 => Ok(Self::KeyHiragana),
|
Windows::KeyLANG2 => Ok(Self::KeyHanja),
|
||||||
Windows::KeyLANG3 => Ok(Self::KeyHenkan),
|
Windows::KeyLANG3 => Ok(Self::KeyKatakana),
|
||||||
Windows::KeyLANG4 => Ok(Self::KeyKatakanahiragana),
|
Windows::KeyLANG4 => Ok(Self::KeyHiragana),
|
||||||
Windows::KeyLeftCtrl => Ok(Self::KeyLeftCtrl),
|
Windows::KeyLeftCtrl => Ok(Self::KeyLeftCtrl),
|
||||||
Windows::KeyLeftShift => Ok(Self::KeyLeftShift),
|
Windows::KeyLeftShift => Ok(Self::KeyLeftShift),
|
||||||
Windows::KeyLeftAlt => Ok(Self::KeyLeftAlt),
|
Windows::KeyLeftAlt => Ok(Self::KeyLeftAlt),
|
||||||
Windows::KeyLeftGUI => Ok(Self::KeyLeftMeta),
|
Windows::KeyLeftGUI => Ok(Self::KeyLeftMeta),
|
||||||
Windows::KeyRightCtrl => Ok(Self::KeyRightCtrl),
|
Windows::KeyRightCtrl => Ok(Self::KeyRightCtrl),
|
||||||
Windows::KeyRightShift => Ok(Self::KeyRightShift),
|
Windows::KeyRightShift => Ok(Self::KeyRightShift),
|
||||||
|
Windows::KeyFakeRightShift => Ok(Self::KeyRightShift),
|
||||||
Windows::KeyRightAlt => Ok(Self::KeyRightalt),
|
Windows::KeyRightAlt => Ok(Self::KeyRightalt),
|
||||||
Windows::KeyRightGUI => Ok(Self::KeyRightmeta),
|
Windows::KeyRightGUI => Ok(Self::KeyRightmeta),
|
||||||
Windows::KeyScanNextTrack => Ok(Self::KeyNextsong),
|
Windows::KeyScanNextTrack => Ok(Self::KeyNextsong),
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use tokio::signal;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
client::{ClientConfig, ClientHandle, ClientManager, ClientState},
|
client::{ClientConfig, ClientHandle, ClientManager, ClientState},
|
||||||
config::Config,
|
config::{CaptureBackend, Config, EmulationBackend},
|
||||||
dns,
|
dns,
|
||||||
frontend::{FrontendListener, FrontendRequest},
|
frontend::{FrontendListener, FrontendRequest},
|
||||||
server::capture_task::CaptureEvent,
|
server::capture_task::CaptureEvent,
|
||||||
@@ -77,7 +77,11 @@ impl Server {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn run(&self) -> anyhow::Result<()> {
|
pub async fn run(
|
||||||
|
&self,
|
||||||
|
capture_backend: Option<CaptureBackend>,
|
||||||
|
emulation_backend: Option<EmulationBackend>,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
// create frontend communication adapter
|
// create frontend communication adapter
|
||||||
let frontend = match FrontendListener::new().await {
|
let frontend = match FrontendListener::new().await {
|
||||||
Some(f) => f?,
|
Some(f) => f?,
|
||||||
@@ -97,20 +101,22 @@ impl Server {
|
|||||||
|
|
||||||
// input capture
|
// input capture
|
||||||
let (mut capture_task, capture_channel) = capture_task::new(
|
let (mut capture_task, capture_channel) = capture_task::new(
|
||||||
|
capture_backend,
|
||||||
self.clone(),
|
self.clone(),
|
||||||
sender_tx.clone(),
|
sender_tx.clone(),
|
||||||
timer_tx.clone(),
|
timer_tx.clone(),
|
||||||
self.release_bind.clone(),
|
self.release_bind.clone(),
|
||||||
);
|
)?;
|
||||||
|
|
||||||
// input emulation
|
// input emulation
|
||||||
let (mut emulation_task, emulate_channel) = emulation_task::new(
|
let (mut emulation_task, emulate_channel) = emulation_task::new(
|
||||||
|
emulation_backend,
|
||||||
self.clone(),
|
self.clone(),
|
||||||
receiver_rx,
|
receiver_rx,
|
||||||
sender_tx.clone(),
|
sender_tx.clone(),
|
||||||
capture_channel.clone(),
|
capture_channel.clone(),
|
||||||
timer_tx,
|
timer_tx,
|
||||||
);
|
)?;
|
||||||
|
|
||||||
// create dns resolver
|
// create dns resolver
|
||||||
let resolver = dns::DnsResolver::new().await?;
|
let resolver = dns::DnsResolver::new().await?;
|
||||||
|
|||||||
@@ -5,8 +5,9 @@ use std::{collections::HashSet, net::SocketAddr};
|
|||||||
use tokio::{process::Command, sync::mpsc::Sender, task::JoinHandle};
|
use tokio::{process::Command, sync::mpsc::Sender, task::JoinHandle};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
capture::{self, InputCapture},
|
capture::{self, error::CaptureCreationError, InputCapture},
|
||||||
client::{ClientEvent, ClientHandle},
|
client::{ClientEvent, ClientHandle},
|
||||||
|
config::CaptureBackend,
|
||||||
event::{Event, KeyboardEvent},
|
event::{Event, KeyboardEvent},
|
||||||
scancode,
|
scancode,
|
||||||
server::State,
|
server::State,
|
||||||
@@ -25,14 +26,15 @@ pub enum CaptureEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(
|
pub fn new(
|
||||||
|
backend: Option<CaptureBackend>,
|
||||||
server: Server,
|
server: Server,
|
||||||
sender_tx: Sender<(Event, SocketAddr)>,
|
sender_tx: Sender<(Event, SocketAddr)>,
|
||||||
timer_tx: Sender<()>,
|
timer_tx: Sender<()>,
|
||||||
release_bind: Vec<scancode::Linux>,
|
release_bind: Vec<scancode::Linux>,
|
||||||
) -> (JoinHandle<Result<()>>, Sender<CaptureEvent>) {
|
) -> Result<(JoinHandle<Result<()>>, Sender<CaptureEvent>), CaptureCreationError> {
|
||||||
let (tx, mut rx) = tokio::sync::mpsc::channel(32);
|
let (tx, mut rx) = tokio::sync::mpsc::channel(32);
|
||||||
let task = tokio::task::spawn_local(async move {
|
let task = tokio::task::spawn_local(async move {
|
||||||
let mut capture = capture::create().await;
|
let mut capture = capture::create(backend).await?;
|
||||||
let mut pressed_keys = HashSet::new();
|
let mut pressed_keys = HashSet::new();
|
||||||
loop {
|
loop {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
@@ -62,7 +64,7 @@ pub fn new(
|
|||||||
}
|
}
|
||||||
anyhow::Ok(())
|
anyhow::Ok(())
|
||||||
});
|
});
|
||||||
(task, tx)
|
Ok((task, tx))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_pressed_keys(pressed_keys: &mut HashSet<scancode::Linux>, key: u32, state: u8) {
|
fn update_pressed_keys(pressed_keys: &mut HashSet<scancode::Linux>, key: u32, state: u8) {
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ use tokio::{
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
client::{ClientEvent, ClientHandle},
|
client::{ClientEvent, ClientHandle},
|
||||||
emulate::{self, InputEmulation},
|
config::EmulationBackend,
|
||||||
|
emulate::{self, error::EmulationCreationError, InputEmulation},
|
||||||
event::{Event, KeyboardEvent},
|
event::{Event, KeyboardEvent},
|
||||||
scancode,
|
scancode,
|
||||||
server::State,
|
server::State,
|
||||||
@@ -27,15 +28,16 @@ pub enum EmulationEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(
|
pub fn new(
|
||||||
|
backend: Option<EmulationBackend>,
|
||||||
server: Server,
|
server: Server,
|
||||||
mut udp_rx: Receiver<Result<(Event, SocketAddr)>>,
|
mut udp_rx: Receiver<Result<(Event, SocketAddr)>>,
|
||||||
sender_tx: Sender<(Event, SocketAddr)>,
|
sender_tx: Sender<(Event, SocketAddr)>,
|
||||||
capture_tx: Sender<CaptureEvent>,
|
capture_tx: Sender<CaptureEvent>,
|
||||||
timer_tx: Sender<()>,
|
timer_tx: Sender<()>,
|
||||||
) -> (JoinHandle<Result<()>>, Sender<EmulationEvent>) {
|
) -> Result<(JoinHandle<Result<()>>, Sender<EmulationEvent>), EmulationCreationError> {
|
||||||
let (tx, mut rx) = tokio::sync::mpsc::channel(32);
|
let (tx, mut rx) = tokio::sync::mpsc::channel(32);
|
||||||
let emulate_task = tokio::task::spawn_local(async move {
|
let emulate_task = tokio::task::spawn_local(async move {
|
||||||
let mut emulate = emulate::create().await;
|
let mut emulate = emulate::create(backend).await?;
|
||||||
let mut last_ignored = None;
|
let mut last_ignored = None;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
@@ -75,7 +77,7 @@ pub fn new(
|
|||||||
emulate.destroy().await;
|
emulate.destroy().await;
|
||||||
anyhow::Ok(())
|
anyhow::Ok(())
|
||||||
});
|
});
|
||||||
(emulate_task, tx)
|
Ok((emulate_task, tx))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_udp_rx(
|
async fn handle_udp_rx(
|
||||||
|
|||||||
@@ -107,7 +107,8 @@ async fn handle_frontend_event(
|
|||||||
log::debug!("frontend: {event:?}");
|
log::debug!("frontend: {event:?}");
|
||||||
match event {
|
match event {
|
||||||
FrontendRequest::Create => {
|
FrontendRequest::Create => {
|
||||||
add_client(server, frontend).await;
|
let handle = add_client(server, frontend).await;
|
||||||
|
resolve_dns(server, resolve_tx, handle).await;
|
||||||
}
|
}
|
||||||
FrontendRequest::Activate(handle, active) => {
|
FrontendRequest::Activate(handle, active) => {
|
||||||
if active {
|
if active {
|
||||||
@@ -140,10 +141,12 @@ async fn handle_frontend_event(
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
FrontendRequest::UpdateFixIps(handle, fix_ips) => {
|
FrontendRequest::UpdateFixIps(handle, fix_ips) => {
|
||||||
update_fix_ips(server, resolve_tx, handle, fix_ips).await;
|
update_fix_ips(server, handle, fix_ips).await;
|
||||||
|
resolve_dns(server, resolve_tx, handle).await;
|
||||||
}
|
}
|
||||||
FrontendRequest::UpdateHostname(handle, hostname) => {
|
FrontendRequest::UpdateHostname(handle, hostname) => {
|
||||||
update_hostname(server, resolve_tx, handle, hostname).await;
|
update_hostname(server, resolve_tx, handle, hostname).await;
|
||||||
|
resolve_dns(server, resolve_tx, handle).await;
|
||||||
}
|
}
|
||||||
FrontendRequest::UpdatePort(handle, port) => {
|
FrontendRequest::UpdatePort(handle, port) => {
|
||||||
update_port(server, handle, port).await;
|
update_port(server, handle, port).await;
|
||||||
@@ -152,31 +155,41 @@ async fn handle_frontend_event(
|
|||||||
update_pos(server, handle, capture, emulate, pos).await;
|
update_pos(server, handle, capture, emulate, pos).await;
|
||||||
}
|
}
|
||||||
FrontendRequest::ResolveDns(handle) => {
|
FrontendRequest::ResolveDns(handle) => {
|
||||||
let hostname = server
|
resolve_dns(server, resolve_tx, handle).await;
|
||||||
.client_manager
|
|
||||||
.borrow()
|
|
||||||
.get(handle)
|
|
||||||
.and_then(|(c, _)| c.hostname.clone());
|
|
||||||
if let Some(hostname) = hostname {
|
|
||||||
let _ = resolve_tx.send(DnsRequest { hostname, handle }).await;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn resolve_dns(server: &Server, resolve_tx: &Sender<DnsRequest>, handle: ClientHandle) {
|
||||||
|
let hostname = server
|
||||||
|
.client_manager
|
||||||
|
.borrow()
|
||||||
|
.get(handle)
|
||||||
|
.and_then(|(c, _)| c.hostname.clone());
|
||||||
|
if let Some(hostname) = hostname {
|
||||||
|
let _ = resolve_tx
|
||||||
|
.send(DnsRequest {
|
||||||
|
hostname: hostname.clone(),
|
||||||
|
handle,
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn broadcast(frontend: &mut FrontendListener, event: FrontendEvent) {
|
async fn broadcast(frontend: &mut FrontendListener, event: FrontendEvent) {
|
||||||
if let Err(e) = frontend.broadcast_event(event).await {
|
if let Err(e) = frontend.broadcast_event(event).await {
|
||||||
log::error!("error notifying frontend: {e}");
|
log::error!("error notifying frontend: {e}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn add_client(server: &Server, frontend: &mut FrontendListener) {
|
pub async fn add_client(server: &Server, frontend: &mut FrontendListener) -> ClientHandle {
|
||||||
let handle = server.client_manager.borrow_mut().add_client();
|
let handle = server.client_manager.borrow_mut().add_client();
|
||||||
log::info!("added client {handle}");
|
log::info!("added client {handle}");
|
||||||
|
|
||||||
let (c, s) = server.client_manager.borrow().get(handle).unwrap().clone();
|
let (c, s) = server.client_manager.borrow().get(handle).unwrap().clone();
|
||||||
broadcast(frontend, FrontendEvent::Created(handle, c, s)).await;
|
broadcast(frontend, FrontendEvent::Created(handle, c, s)).await;
|
||||||
|
handle
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn deactivate_client(
|
pub async fn deactivate_client(
|
||||||
@@ -251,25 +264,13 @@ pub async fn remove_client(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn update_fix_ips(
|
async fn update_fix_ips(server: &Server, handle: ClientHandle, fix_ips: Vec<IpAddr>) {
|
||||||
server: &Server,
|
let mut client_manager = server.client_manager.borrow_mut();
|
||||||
resolve_tx: &Sender<DnsRequest>,
|
let Some((c, _)) = client_manager.get_mut(handle) else {
|
||||||
handle: ClientHandle,
|
return;
|
||||||
fix_ips: Vec<IpAddr>,
|
|
||||||
) {
|
|
||||||
let hostname = {
|
|
||||||
let mut client_manager = server.client_manager.borrow_mut();
|
|
||||||
let Some((c, _)) = client_manager.get_mut(handle) else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
c.fix_ips = fix_ips;
|
|
||||||
c.hostname.clone()
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(hostname) = hostname {
|
c.fix_ips = fix_ips;
|
||||||
let _ = resolve_tx.send(DnsRequest { hostname, handle }).await;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn update_hostname(
|
async fn update_hostname(
|
||||||
|
|||||||
Reference in New Issue
Block a user