Background service (#43)

better handling of background-service: lan-mouse can now be run without a gui by specifying --daemon as an argument.
Otherwise the servic will be run as a child process and correctly terminate when the window is closed / frontend exits.

Closes #38
This commit is contained in:
Ferdinand Schober
2023-12-09 00:36:01 +01:00
committed by GitHub
parent 9b242f6138
commit 56e5f7a30d
13 changed files with 488 additions and 170 deletions

219
Cargo.lock generated
View File

@@ -26,6 +26,54 @@ dependencies = [
"memchr",
]
[[package]]
name = "anstream"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87"
[[package]]
name = "anstyle-parse"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3a318f1f38d2418400f8209655bfd825785afd25aa30bb7ba6cc792e4596748"
dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
dependencies = [
"anstyle",
"windows-sys 0.52.0",
]
[[package]]
name = "anyhow"
version = "1.0.75"
@@ -115,7 +163,7 @@ dependencies = [
"futures-lite",
"rustix 0.37.24",
"signal-hook",
"windows-sys",
"windows-sys 0.48.0",
]
[[package]]
@@ -272,6 +320,52 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "4.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfaff671f6b22ca62406885ece523383b9b64022e341e53e009a62ebc47a45f2"
dependencies = [
"clap_builder",
"clap_derive",
]
[[package]]
name = "clap_builder"
version = "4.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a216b506622bb1d316cd51328dce24e07bdff4a6128a47c7e7fad11878d5adbb"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_derive"
version = "4.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.37",
]
[[package]]
name = "clap_lex"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1"
[[package]]
name = "colorchoice"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
[[package]]
name = "concurrent-queue"
version = "2.3.0"
@@ -411,7 +505,7 @@ checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd"
dependencies = [
"errno-dragonfly",
"libc",
"windows-sys",
"windows-sys 0.48.0",
]
[[package]]
@@ -935,7 +1029,7 @@ checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2"
dependencies = [
"hermit-abi",
"libc",
"windows-sys",
"windows-sys 0.48.0",
]
[[package]]
@@ -946,7 +1040,7 @@ checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f"
dependencies = [
"socket2 0.5.4",
"widestring",
"windows-sys",
"windows-sys 0.48.0",
"winreg",
]
@@ -964,7 +1058,7 @@ checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
dependencies = [
"hermit-abi",
"rustix 0.38.13",
"windows-sys",
"windows-sys 0.48.0",
]
[[package]]
@@ -980,6 +1074,7 @@ dependencies = [
"anyhow",
"ashpd",
"async-trait",
"clap",
"env_logger",
"futures",
"futures-core",
@@ -1050,7 +1145,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d580318f95776505201b28cf98eb1fa5e4be3b689633ba6a3e6cd880ff22d8cb"
dependencies = [
"cfg-if",
"windows-sys",
"windows-sys 0.48.0",
]
[[package]]
@@ -1153,7 +1248,7 @@ checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2"
dependencies = [
"libc",
"wasi",
"windows-sys",
"windows-sys 0.48.0",
]
[[package]]
@@ -1254,7 +1349,7 @@ dependencies = [
"libc",
"redox_syscall",
"smallvec",
"windows-targets",
"windows-targets 0.48.5",
]
[[package]]
@@ -1305,7 +1400,7 @@ dependencies = [
"libc",
"log",
"pin-project-lite",
"windows-sys",
"windows-sys 0.48.0",
]
[[package]]
@@ -1495,7 +1590,7 @@ dependencies = [
"io-lifetimes",
"libc",
"linux-raw-sys 0.3.8",
"windows-sys",
"windows-sys 0.48.0",
]
[[package]]
@@ -1508,7 +1603,7 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys 0.4.7",
"windows-sys",
"windows-sys 0.48.0",
]
[[package]]
@@ -1648,7 +1743,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e"
dependencies = [
"libc",
"windows-sys",
"windows-sys 0.48.0",
]
[[package]]
@@ -1657,6 +1752,12 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "syn"
version = "1.0.109"
@@ -1708,7 +1809,7 @@ dependencies = [
"fastrand 2.0.0",
"redox_syscall",
"rustix 0.38.13",
"windows-sys",
"windows-sys 0.48.0",
]
[[package]]
@@ -1771,7 +1872,7 @@ dependencies = [
"socket2 0.5.4",
"tokio-macros",
"tracing",
"windows-sys",
"windows-sys 0.48.0",
]
[[package]]
@@ -1946,6 +2047,12 @@ dependencies = [
"serde",
]
[[package]]
name = "utf8parse"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]]
name = "version-compare"
version = "0.1.1"
@@ -2113,7 +2220,16 @@ version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets",
"windows-targets 0.48.5",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets 0.52.0",
]
[[package]]
@@ -2122,13 +2238,28 @@ version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
"windows_aarch64_gnullvm 0.48.5",
"windows_aarch64_msvc 0.48.5",
"windows_i686_gnu 0.48.5",
"windows_i686_msvc 0.48.5",
"windows_x86_64_gnu 0.48.5",
"windows_x86_64_gnullvm 0.48.5",
"windows_x86_64_msvc 0.48.5",
]
[[package]]
name = "windows-targets"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
dependencies = [
"windows_aarch64_gnullvm 0.52.0",
"windows_aarch64_msvc 0.52.0",
"windows_i686_gnu 0.52.0",
"windows_i686_msvc 0.52.0",
"windows_x86_64_gnu 0.52.0",
"windows_x86_64_gnullvm 0.52.0",
"windows_x86_64_msvc 0.52.0",
]
[[package]]
@@ -2137,42 +2268,84 @@ version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]]
name = "windows_i686_gnu"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]]
name = "windows_i686_msvc"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
[[package]]
name = "winnow"
version = "0.5.15"
@@ -2189,7 +2362,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
dependencies = [
"cfg-if",
"windows-sys",
"windows-sys 0.48.0",
]
[[package]]

View File

@@ -27,6 +27,7 @@ tokio = {version = "1.32.0", features = ["io-util", "macros", "net", "rt", "sync
async-trait = "0.1.73"
futures-core = "0.3.28"
futures = "0.3.28"
clap = { version="4.4.11", features = ["derive"] }
[target.'cfg(unix)'.dependencies]
wayland-client = { version="0.30.2", optional = true }

View File

@@ -10,6 +10,7 @@
</menu>
<template class="LanMouseWindow" parent="AdwApplicationWindow">
<property name="width-request">600</property>
<property name="height-request">700</property>
<property name="title" translatable="yes">Lan Mouse</property>
<property name="show-menubar">True</property>
<property name="content">
@@ -45,6 +46,7 @@
<child>
<object class="AdwPreferencesGroup">
<property name="title" translatable="yes">General</property>
<!--
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">enable</property>
@@ -56,6 +58,7 @@
</child>
</object>
</child>
-->
<child>
<object class="AdwActionRow">
<property name="title">port</property>
@@ -98,13 +101,27 @@
<child>
<object class="AdwPreferencesGroup">
<property name="title" translatable="yes">Connections</property>
<property name="header-suffix">
<object class="GtkButton">
<signal name="clicked" handler="handle_add_client_pressed" swapped="true"/>
<property name="child">
<object class="AdwButtonContent">
<property name="icon-name">list-add-symbolic</property>
<property name="label" translatable="yes">Add</property>
</object>
</property>
<style>
<class name="flat"/>
</style>
</object>
</property>
<child>
<object class="GtkListBox" id="client_list">
<property name="selection-mode">none</property>
<child type="placeholder">
<object class="AdwActionRow" id="client_placeholder">
<property name="title">No connections!</property>
<property name="subtitle">add a new client via the button below</property>
<property name="subtitle">add a new client via the + button</property>
</object>
</child>
<style>
@@ -114,28 +131,6 @@
</child>
</object>
</child>
<child>
<object class="AdwPreferencesGroup">
<child>
<object class="GtkButton" id="add_client_button">
<property name="halign">center</property>
<property name="valign">center</property>
<property name="tooltip-text">connect a new computer</property>
<property name="child">
<object class="AdwButtonContent">
<property name="icon-name">list-add-symbolic</property>
<property name="label" translatable="yes">Add</property>
<property name="use-underline">True</property>
</object>
</property>
<signal name="clicked" handler="handle_add_client_pressed" swapped="true"/>
<style>
<class name="pill"/>
</style>
</object>
</child>
</object>
</child>
</object>
</property>
</object>

View File

@@ -1,4 +1,3 @@
use anyhow::Result;
use async_trait::async_trait;
use crate::{event::{KeyboardEvent, PointerEvent}, consumer::EventConsumer};
use winapi::{

View File

@@ -1,4 +1,5 @@
use std::{error::Error, io, result::Result, task::Poll};
use anyhow::Result;
use std::{io, task::Poll};
use futures_core::Stream;
@@ -7,7 +8,7 @@ use crate::{producer::EventProducer, event::Event, client::ClientHandle};
pub struct LibeiProducer {}
impl LibeiProducer {
pub fn new() -> Result<Self, Box<dyn Error>> {
pub fn new() -> Result<Self> {
Ok(Self { })
}
}

View File

@@ -1,11 +1,11 @@
use anyhow::Result;
use serde::{Deserialize, Serialize};
use core::fmt;
use std::collections::HashSet;
use std::net::IpAddr;
use std::{error::Error, fs};
use std::env;
use toml;
use clap::Parser;
use crate::client::Position;
@@ -28,19 +28,6 @@ pub struct Client {
pub port: Option<u16>,
}
#[derive(Debug, Clone)]
struct MissingParameter {
arg: &'static str,
}
impl fmt::Display for MissingParameter {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Missing a parameter for argument: {}", self.arg)
}
}
impl Error for MissingParameter {}
impl ConfigToml {
pub fn new(path: &str) -> Result<ConfigToml, Box<dyn Error>> {
let config = fs::read_to_string(path)?;
@@ -49,35 +36,43 @@ impl ConfigToml {
}
}
fn find_arg(key: &'static str) -> Result<Option<String>, MissingParameter> {
let args: Vec<String> = env::args().collect();
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct CliArgs {
/// the listen port for lan-mouse
#[arg(short, long)]
port: Option<u16>,
for (i, arg) in args.iter().enumerate() {
if arg != key {
continue;
}
match args.get(i+1) {
None => return Err(MissingParameter { arg: key }),
Some(arg) => return Ok(Some(arg.clone())),
};
}
Ok(None)
/// the frontend to use [cli | gtk]
#[arg(short, long)]
frontend: Option<String>,
/// non-default config file location
#[arg(short, long)]
config: Option<String>,
/// run only the service as a daemon without the frontend
#[arg(short, long)]
daemon: bool,
}
#[derive(PartialEq, Eq)]
#[derive(Debug, PartialEq, Eq)]
pub enum Frontend {
Gtk,
Cli,
}
#[derive(Debug)]
pub struct Config {
pub frontend: Frontend,
pub port: u16,
pub clients: Vec<(Client, Position)>,
pub daemon: bool,
}
impl Config {
pub fn new() -> Result<Self, Box<dyn Error>> {
pub fn new() -> Result<Self> {
let args = CliArgs::parse();
let config_file = "config.toml";
#[cfg(unix)] let config_path = {
let xdg_config_home = env::var("XDG_CONFIG_HOME")
@@ -92,7 +87,7 @@ impl Config {
};
// --config <file> overrules default location
let config_path = find_arg("--config")?.unwrap_or(config_path);
let config_path = args.config.unwrap_or(config_path);
let config_toml = match ConfigToml::new(config_path.as_str()) {
Err(e) => {
@@ -103,7 +98,7 @@ impl Config {
Ok(c) => Some(c),
};
let frontend = match find_arg("--frontend")? {
let frontend = match args.frontend {
None => match &config_toml {
Some(c) => c.frontend.clone(),
None => None,
@@ -123,8 +118,8 @@ impl Config {
}
};
let port = match find_arg("--port")? {
Some(port) => port.parse::<u16>()?,
let port = match args.port {
Some(port) => port,
None => match &config_toml {
Some(c) => c.port.unwrap_or(DEFAULT_PORT),
None => DEFAULT_PORT,
@@ -148,7 +143,14 @@ impl Config {
}
}
Ok(Config { frontend, clients, port })
let daemon = args.daemon;
Ok(Config {
daemon,
frontend,
clients,
port,
})
}
pub fn get_clients(&self) -> Vec<(HashSet<IpAddr>, Option<String>, u16, Position)> {

View File

@@ -1,5 +1,5 @@
use std::io::Result;
use std::str;
use anyhow::{Result, anyhow};
use std::{str, io::ErrorKind, time::Duration, cmp::min};
#[cfg(unix)]
use std::{env, path::{Path, PathBuf}};
@@ -11,6 +11,7 @@ use tokio::io::ReadHalf;
use tokio::net::UnixStream;
#[cfg(unix)]
use tokio::net::UnixListener;
#[cfg(windows)]
use tokio::net::TcpStream;
#[cfg(windows)]
@@ -18,7 +19,7 @@ use tokio::net::TcpListener;
use serde::{Serialize, Deserialize};
use crate::client::{Position, ClientHandle, Client};
use crate::{client::{Position, ClientHandle, Client}, config::{Config, Frontend}};
/// cli frontend
pub mod cli;
@@ -27,6 +28,52 @@ pub mod cli;
#[cfg(all(unix, feature = "gtk"))]
pub mod gtk;
pub fn run_frontend(config: &Config) -> Result<()> {
match config.frontend {
#[cfg(all(unix, feature = "gtk"))]
Frontend::Gtk => { gtk::run(); }
#[cfg(any(not(feature = "gtk"), not(unix)))]
Frontend::Gtk => panic!("gtk frontend requested but feature not enabled!"),
Frontend::Cli => { cli::run()?; }
};
Ok(())
}
fn exponential_back_off(duration: &mut Duration) -> &Duration {
let new = duration.saturating_mul(2);
*duration = min(new, Duration::from_secs(1));
duration
}
/// wait for the lan-mouse socket to come online
#[cfg(unix)]
pub fn wait_for_service() -> Result<std::os::unix::net::UnixStream> {
let socket_path = FrontendListener::socket_path()?;
let mut duration = Duration::from_millis(1);
loop {
use std::os::unix::net::UnixStream;
if let Ok(stream) = UnixStream::connect(&socket_path) {
break Ok(stream)
}
// a signaling mechanism or inotify could be used to
// improve this
std::thread::sleep(*exponential_back_off(&mut duration));
}
}
#[cfg(windows)]
pub fn wait_for_service() -> Result<std::net::TcpStream> {
let mut duration = Duration::from_millis(1);
loop {
use std::net::TcpStream;
if let Ok(stream) = TcpStream::connect("127.0.0.1:5252") {
break Ok(stream)
}
std::thread::sleep(*exponential_back_off(&mut duration));
}
}
#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
pub enum FrontendEvent {
/// add a new client
@@ -70,20 +117,54 @@ pub struct FrontendListener {
}
impl FrontendListener {
pub async fn new() -> std::result::Result<Self, Box<dyn std::error::Error>> {
#[cfg(unix)]
pub fn socket_path() -> Result<PathBuf> {
let xdg_runtime_dir = match env::var("XDG_RUNTIME_DIR") {
Ok(d) => d,
Err(e) => return Err(anyhow!("could not find XDG_RUNTIME_DIR: {e}")),
};
let xdg_runtime_dir = Path::new(xdg_runtime_dir.as_str());
Ok(xdg_runtime_dir.join("lan-mouse-socket.sock"))
}
pub async fn new() -> Option<Result<Self>> {
#[cfg(unix)]
let socket_path = Path::new(env::var("XDG_RUNTIME_DIR")?.as_str()).join("lan-mouse-socket.sock");
#[cfg(unix)]
log::debug!("remove socket: {:?}", socket_path);
#[cfg(unix)]
if socket_path.exists() {
std::fs::remove_file(&socket_path).unwrap();
}
#[cfg(unix)]
let listener = UnixListener::bind(&socket_path)?;
let (socket_path, listener) = {
let socket_path = match Self::socket_path() {
Ok(path) => path,
Err(e) => return Some(Err(e)),
};
log::debug!("remove socket: {:?}", socket_path);
if socket_path.exists() {
// try to connect to see if some other instance
// of lan-mouse is already running
match UnixStream::connect(&socket_path).await {
// connected -> lan-mouse is already running
Ok(_) => return None,
// lan-mouse is not running but a socket was left behind
Err(e) => {
log::debug!("{socket_path:?}: {e} - removing left behind socket");
let _ = std::fs::remove_file(&socket_path);
},
}
}
let listener = match UnixListener::bind(&socket_path) {
Ok(ls) => ls,
// some other lan-mouse instance has bound the socket in the meantime
Err(e) if e.kind() == ErrorKind::AddrInUse => return None,
Err(e) => return Some(Err(anyhow!("failed to bind lan-mouse-socket: {e}"))),
};
(socket_path, listener)
};
#[cfg(windows)]
let listener = TcpListener::bind("127.0.0.1:5252").await?; // abuse tcp
let listener = match TcpListener::bind("127.0.0.1:5252").await {
Ok(ls) => ls,
// some other lan-mouse instance has bound the socket in the meantime
Err(e) if e.kind() == ErrorKind::AddrInUse => return None,
Err(e) => return Some(Err(anyhow!("failed to bind lan-mouse-socket: {e}"))),
};
let adapter = Self {
listener,
@@ -92,7 +173,7 @@ impl FrontendListener {
tx_streams: vec![],
};
Ok(adapter)
Some(Ok(adapter))
}
#[cfg(unix)]
@@ -121,12 +202,25 @@ impl FrontendListener {
let len = payload.len().to_be_bytes();
log::debug!("json: {json}, len: {}", payload.len());
let mut keep = vec![];
// TODO do simultaneously
for tx in self.tx_streams.iter_mut() {
// write len + payload
tx.write(&len).await?;
tx.write(payload).await?;
if let Err(_) = tx.write(&len).await {
keep.push(false);
continue;
}
if let Err(_) = tx.write(payload).await {
keep.push(false);
continue;
}
keep.push(true);
}
// could not find a better solution because async
let mut keep = keep.into_iter();
self.tx_streams.retain(|_| keep.next().unwrap());
Ok(())
}
}

View File

@@ -1,10 +1,10 @@
use anyhow::{anyhow, Result, Context};
use std::{thread::{self, JoinHandle}, io::{Write, Read, ErrorKind}, str::SplitWhitespace};
use std::{thread, io::{Write, Read, ErrorKind}, str::SplitWhitespace};
#[cfg(windows)]
use std::net::SocketAddrV4;
#[cfg(unix)]
use std::{os::unix::net::UnixStream, path::Path, env};
use std::os::unix::net::UnixStream;
#[cfg(windows)]
use std::net::TcpStream;
@@ -12,9 +12,9 @@ use crate::{client::Position, config::DEFAULT_PORT};
use super::{FrontendEvent, FrontendNotify};
pub fn start() -> Result<(JoinHandle<()>, JoinHandle<()>)> {
pub fn run() -> Result<()> {
#[cfg(unix)]
let socket_path = Path::new(env::var("XDG_RUNTIME_DIR")?.as_str()).join("lan-mouse-socket.sock");
let socket_path = super::FrontendListener::socket_path()?;
#[cfg(unix)]
let Ok(mut tx) = UnixStream::connect(&socket_path) else {
@@ -50,7 +50,7 @@ pub fn start() -> Result<(JoinHandle<()>, JoinHandle<()>)> {
log::error!("error sending message: {e}");
};
if *event == FrontendEvent::Shutdown() {
break;
return;
}
}
// prompt is printed after the server response is received
@@ -126,7 +126,29 @@ pub fn start() -> Result<(JoinHandle<()>, JoinHandle<()>)> {
prompt();
}
})?;
Ok((reader, writer))
match reader.join() {
Ok(_) => (),
Err(e) => {
let msg = match (e.downcast_ref::<&str>(), e.downcast_ref::<String>()) {
(Some(&s), _) => s,
(_, Some(s)) => s,
_ => "no panic info"
};
log::error!("reader thread paniced: {msg}");
},
}
match writer.join() {
Ok(_) => (),
Err(e) => {
let msg = match (e.downcast_ref::<&str>(), e.downcast_ref::<String>()) {
(Some(&s), _) => s,
(_, Some(s)) => s,
_ => "no panic info"
};
log::error!("writer thread paniced: {msg}");
},
}
Ok(())
}
fn prompt() {

View File

@@ -2,7 +2,7 @@ mod window;
mod client_object;
mod client_row;
use std::{io::{Result, Read, ErrorKind}, thread::{self, JoinHandle}, env, process, path::Path, os::unix::net::UnixStream, str};
use std::{io::{Read, ErrorKind}, env, process, str};
use crate::{frontend::gtk::window::Window, config::DEFAULT_PORT};
@@ -14,11 +14,11 @@ use self::client_object::ClientObject;
use super::FrontendNotify;
pub fn start() -> Result<JoinHandle<glib::ExitCode>> {
log::debug!("starting gtk frontend");
thread::Builder::new()
.name("gtk-thread".into())
.spawn(gtk_main)
pub fn run() -> glib::ExitCode {
log::debug!("running gtk frontend");
let ret = gtk_main();
log::debug!("frontend exited");
ret
}
fn gtk_main() -> glib::ExitCode {
@@ -33,7 +33,8 @@ fn gtk_main() -> glib::ExitCode {
app.connect_startup(|_| load_css());
app.connect_activate(build_ui);
app.run()
let args: Vec<&'static str> = vec![];
app.run_with_args(&args)
}
fn load_css() {
@@ -52,20 +53,14 @@ fn load_icons() {
}
fn build_ui(app: &Application) {
let xdg_runtime_dir = match env::var("XDG_RUNTIME_DIR") {
Ok(v) => v,
log::debug!("connecting to lan-mouse-socket");
let mut rx = match super::wait_for_service() {
Ok(stream) => stream,
Err(e) => {
log::error!("{e}");
log::error!("could not connect to lan-mouse-socket: {e}");
process::exit(1);
}
};
log::debug!("connecting to lan-mouse-socket ... ");
let socket_path = Path::new(xdg_runtime_dir.as_str())
.join("lan-mouse-socket.sock");
let Ok(mut rx) = UnixStream::connect(&socket_path) else {
log::error!("Could not connect to lan-mouse-socket @ {socket_path:?}");
process::exit(1);
};
let tx = match rx.try_clone() {
Ok(sock) => sock,
Err(e) => {

View File

@@ -15,8 +15,6 @@ pub struct Window {
#[template_child]
pub port_edit_cancel: TemplateChild<Button>,
#[template_child]
pub add_client_button: TemplateChild<Button>,
#[template_child]
pub client_list: TemplateChild<ListBox>,
#[template_child]
pub client_placeholder: TemplateChild<ActionRow>,

View File

@@ -1,15 +1,14 @@
use std::{process, error::Error};
use anyhow::Result;
use std::process::{self, Command, Child};
use env_logger::Env;
use lan_mouse::{
consumer, producer,
config::{Config, Frontend::{Cli, Gtk}}, server::Server,
frontend::{FrontendListener, cli},
config::Config, server::Server,
frontend::{FrontendListener, self},
};
#[cfg(all(unix, feature = "gtk"))]
use lan_mouse::frontend::gtk;
use tokio::task::LocalSet;
use tokio::{task::LocalSet, join};
pub fn main() {
@@ -23,10 +22,36 @@ pub fn main() {
}
}
pub fn run() -> Result<(), Box<dyn Error>> {
// parse config file
let config = Config::new()?;
pub fn start_service() -> Result<Child> {
let child = Command::new(std::env::current_exe()?)
.args(std::env::args().skip(1))
.arg("--daemon")
.spawn()?;
Ok(child)
}
pub fn run() -> Result<()> {
// parse config file + cli args
let config = Config::new()?;
log::debug!("{config:?}");
if config.daemon {
// if daemon is specified we run the service
run_service(&config)?;
} else {
// otherwise start the service as a child process and
// run a frontend
start_service()?;
frontend::run_frontend(&config)?;
}
anyhow::Ok(())
}
fn run_service(config: &Config) -> Result<()> {
// create single threaded tokio runtime
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_io()
.enable_time()
@@ -34,37 +59,33 @@ pub fn run() -> Result<(), Box<dyn Error>> {
// run async event loop
runtime.block_on(LocalSet::new().run_until(async {
// start producing and consuming events
let producer = producer::create()?;
let consumer = consumer::create().await?;
// create frontend communication adapter
let frontend_adapter = FrontendListener::new().await?;
// start frontend
match config.frontend {
#[cfg(all(unix, feature = "gtk"))]
Gtk => { gtk::start()?; }
#[cfg(any(not(feature = "gtk"), not(unix)))]
Gtk => panic!("gtk frontend requested but feature not enabled!"),
Cli => { cli::start()?; }
let frontend_adapter = match FrontendListener::new().await {
Some(Err(e)) => return Err(e),
Some(Ok(f)) => f,
None => {
// none means some other instance is already running
log::info!("service already running, exiting");
return anyhow::Ok(())
}
,
};
// start sending and receiving events
let mut event_server = Server::new(config.port, frontend_adapter, consumer, producer).await?;
log::debug!("created server");
// add clients from config
for (c,h,port,p) in config.get_clients().into_iter() {
event_server.add_client(h, c, port, p).await;
}
// create event producer and consumer
let (producer, consumer) = join!(
producer::create(),
consumer::create(),
);
let (producer, consumer) = (producer?, consumer?);
// create server
let mut event_server = Server::new(config, frontend_adapter, consumer, producer).await?;
log::info!("Press Ctrl+Alt+Shift+Super to release the mouse");
// run event loop
event_server.run().await?;
Result::<_, Box<dyn Error>>::Ok(())
log::debug!("service exiting");
anyhow::Ok(())
}))?;
log::debug!("exiting main");
Ok(())
}

View File

@@ -1,4 +1,5 @@
use std::{error::Error, io};
use anyhow::Result;
use std::io;
use futures_core::Stream;
@@ -15,7 +16,7 @@ enum Backend {
X11,
}
pub fn create() -> Result<Box<dyn EventProducer>, Box<dyn Error>> {
pub async fn create() -> Result<Box<dyn EventProducer>> {
#[cfg(windows)]
return Ok(Box::new(producer::windows::WindowsProducer::new()));

View File

@@ -11,7 +11,7 @@ use tokio::net::TcpStream;
use std::{net::SocketAddr, io::ErrorKind};
use crate::{client::{ClientEvent, ClientManager, Position, ClientHandle}, consumer::EventConsumer, producer::EventProducer, frontend::{FrontendEvent, FrontendListener, FrontendNotify, self}, dns::{self, DnsResolver}};
use crate::{client::{ClientEvent, ClientManager, Position, ClientHandle}, consumer::EventConsumer, producer::EventProducer, frontend::{FrontendEvent, FrontendListener, FrontendNotify, self}, dns::{self, DnsResolver}, config::Config};
use crate::event::Event;
/// keeps track of state to prevent a feedback loop
@@ -36,7 +36,7 @@ pub struct Server {
impl Server {
pub async fn new(
port: u16,
config: &Config,
frontend: FrontendListener,
consumer: Box<dyn EventConsumer>,
producer: Box<dyn EventProducer>,
@@ -46,13 +46,13 @@ impl Server {
let resolver = dns::DnsResolver::new().await?;
// bind the udp socket
let listen_addr = SocketAddr::new("0.0.0.0".parse().unwrap(), port);
let listen_addr = SocketAddr::new("0.0.0.0".parse().unwrap(), config.port);
let socket = UdpSocket::bind(listen_addr).await?;
let (frontend_tx, frontend_rx) = tokio::sync::mpsc::channel(1);
// create client manager
let client_manager = ClientManager::new();
Ok(Server {
let mut server = Server {
frontend,
consumer,
producer,
@@ -62,7 +62,14 @@ impl Server {
state: State::Receiving,
frontend_rx,
frontend_tx,
})
};
// add clients from config
for (c,h,port,p) in config.get_clients().into_iter() {
server.add_client(h, c, port, p).await;
}
Ok(server)
}
pub async fn run(&mut self) -> anyhow::Result<()> {
@@ -85,7 +92,7 @@ impl Server {
Some(Ok((client, event))) => {
self.handle_producer_event(client,event).await;
},
Some(Err(e)) => log::error!("{e}"),
Some(Err(e)) => log::error!("error reading from event producer: {e}"),
_ => break,
}
}
@@ -145,7 +152,7 @@ impl Server {
log::debug!("add_client {client}");
let notify = FrontendNotify::NotifyClientCreate(client, hostname, port, pos);
if let Err(e) = self.frontend.notify_all(notify).await {
log::error!("{e}");
log::error!("error notifying frontend: {e}");
};
client
}
@@ -170,7 +177,7 @@ impl Server {
let notify = FrontendNotify::NotifyClientDelete(client);
log::debug!("{notify:?}");
if let Err(e) = self.frontend.notify_all(notify).await {
log::error!("{e}");
log::error!("error notifying frontend: {e}");
}
Some(client)
} else {
@@ -367,13 +374,22 @@ impl Server {
#[cfg(unix)]
async fn handle_frontend_stream(&mut self, mut stream: ReadHalf<UnixStream>) {
use std::io;
let tx = self.frontend_tx.clone();
tokio::task::spawn_local(async move {
loop {
let event = frontend::read_event(&mut stream).await;
match event {
Ok(event) => tx.send(event).await.unwrap(),
Err(e) => log::error!("error reading frontend event: {e}"),
Err(e) => {
if let Some(e) = e.downcast_ref::<io::Error>() {
if e.kind() == ErrorKind::UnexpectedEof {
return;
}
}
log::error!("error reading frontend event: {e}");
}
}
}
});
@@ -439,7 +455,7 @@ impl Server {
async fn enumerate(&mut self) {
let clients = self.client_manager.enumerate();
if let Err(e) = self.frontend.notify_all(FrontendNotify::Enumerate(clients)).await {
log::error!("{e}");
log::error!("error notifying frontend: {e}");
}
}
}