mirror of
https://github.com/feschber/lan-mouse.git
synced 2026-03-10 06:40:56 +03:00
Compare commits
1 Commits
rework-cli
...
feschber-p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
79cb111e95 |
4
Cargo.lock
generated
4
Cargo.lock
generated
@@ -1,6 +1,6 @@
|
|||||||
# This file is automatically @generated by Cargo.
|
# This file is automatically @generated by Cargo.
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 4
|
version = 3
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "addr2line"
|
name = "addr2line"
|
||||||
@@ -2005,10 +2005,8 @@ dependencies = [
|
|||||||
name = "lan-mouse-cli"
|
name = "lan-mouse-cli"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
|
||||||
"futures",
|
"futures",
|
||||||
"lan-mouse-ipc",
|
"lan-mouse-ipc",
|
||||||
"thiserror 2.0.0",
|
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -288,10 +288,10 @@ $ cargo run --release -- --frontend cli
|
|||||||
|
|
||||||
Lan Mouse can be launched in daemon mode to keep it running in the background (e.g. for use in a systemd-service).
|
Lan Mouse can be launched in daemon mode to keep it running in the background (e.g. for use in a systemd-service).
|
||||||
|
|
||||||
To do so, use the `daemon` subcommand:
|
To do so, add `--daemon` to the commandline args:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
lan-mouse daemon
|
lan-mouse --daemon
|
||||||
```
|
```
|
||||||
|
|
||||||
In order to start lan-mouse with a graphical session automatically,
|
In order to start lan-mouse with a graphical session automatically,
|
||||||
|
|||||||
@@ -9,8 +9,6 @@ repository = "https://github.com/feschber/lan-mouse"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
futures = "0.3.30"
|
futures = "0.3.30"
|
||||||
lan-mouse-ipc = { path = "../lan-mouse-ipc", version = "0.2.0" }
|
lan-mouse-ipc = { path = "../lan-mouse-ipc", version = "0.2.0" }
|
||||||
clap = { version = "4.4.11", features = ["derive"] }
|
|
||||||
thiserror = "2.0.0"
|
|
||||||
tokio = { version = "1.32.0", features = [
|
tokio = { version = "1.32.0", features = [
|
||||||
"io-util",
|
"io-util",
|
||||||
"io-std",
|
"io-std",
|
||||||
|
|||||||
153
lan-mouse-cli/src/command.rs
Normal file
153
lan-mouse-cli/src/command.rs
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
use std::{
|
||||||
|
fmt::Display,
|
||||||
|
str::{FromStr, SplitWhitespace},
|
||||||
|
};
|
||||||
|
|
||||||
|
use lan_mouse_ipc::{ClientHandle, Position};
|
||||||
|
|
||||||
|
pub(super) enum CommandType {
|
||||||
|
NoCommand,
|
||||||
|
Help,
|
||||||
|
Connect,
|
||||||
|
Disconnect,
|
||||||
|
Activate,
|
||||||
|
Deactivate,
|
||||||
|
List,
|
||||||
|
SetHost,
|
||||||
|
SetPort,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(super) struct InvalidCommand {
|
||||||
|
cmd: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for InvalidCommand {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "invalid command: \"{}\"", self.cmd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for CommandType {
|
||||||
|
type Err = InvalidCommand;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> std::prelude::v1::Result<Self, Self::Err> {
|
||||||
|
match s {
|
||||||
|
"connect" => Ok(Self::Connect),
|
||||||
|
"disconnect" => Ok(Self::Disconnect),
|
||||||
|
"activate" => Ok(Self::Activate),
|
||||||
|
"deactivate" => Ok(Self::Deactivate),
|
||||||
|
"list" => Ok(Self::List),
|
||||||
|
"set-host" => Ok(Self::SetHost),
|
||||||
|
"set-port" => Ok(Self::SetPort),
|
||||||
|
"help" => Ok(Self::Help),
|
||||||
|
_ => Err(InvalidCommand { cmd: s.to_string() }),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(super) enum Command {
|
||||||
|
None,
|
||||||
|
Help,
|
||||||
|
Connect(Position, String, Option<u16>),
|
||||||
|
Disconnect(ClientHandle),
|
||||||
|
Activate(ClientHandle),
|
||||||
|
Deactivate(ClientHandle),
|
||||||
|
List,
|
||||||
|
SetHost(ClientHandle, String),
|
||||||
|
SetPort(ClientHandle, Option<u16>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CommandType {
|
||||||
|
pub(super) fn usage(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
CommandType::Help => "help",
|
||||||
|
CommandType::NoCommand => "",
|
||||||
|
CommandType::Connect => "connect left|right|top|bottom <host> [<port>]",
|
||||||
|
CommandType::Disconnect => "disconnect <id>",
|
||||||
|
CommandType::Activate => "activate <id>",
|
||||||
|
CommandType::Deactivate => "deactivate <id>",
|
||||||
|
CommandType::List => "list",
|
||||||
|
CommandType::SetHost => "set-host <id> <host>",
|
||||||
|
CommandType::SetPort => "set-port <id> <host>",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) enum CommandParseError {
|
||||||
|
Usage(CommandType),
|
||||||
|
Invalid(InvalidCommand),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for CommandParseError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Usage(cmd) => write!(f, "usage: {}", cmd.usage()),
|
||||||
|
Self::Invalid(cmd) => write!(f, "{}", cmd),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Command {
|
||||||
|
type Err = CommandParseError;
|
||||||
|
|
||||||
|
fn from_str(cmd: &str) -> Result<Self, Self::Err> {
|
||||||
|
let mut args = cmd.split_whitespace();
|
||||||
|
let cmd_type: CommandType = match args.next() {
|
||||||
|
Some(c) => c.parse().map_err(CommandParseError::Invalid),
|
||||||
|
None => Ok(CommandType::NoCommand),
|
||||||
|
}?;
|
||||||
|
match cmd_type {
|
||||||
|
CommandType::Help => Ok(Command::Help),
|
||||||
|
CommandType::NoCommand => Ok(Command::None),
|
||||||
|
CommandType::Connect => parse_connect_cmd(args),
|
||||||
|
CommandType::Disconnect => parse_disconnect_cmd(args),
|
||||||
|
CommandType::Activate => parse_activate_cmd(args),
|
||||||
|
CommandType::Deactivate => parse_deactivate_cmd(args),
|
||||||
|
CommandType::List => Ok(Command::List),
|
||||||
|
CommandType::SetHost => parse_set_host(args),
|
||||||
|
CommandType::SetPort => parse_set_port(args),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_connect_cmd(mut args: SplitWhitespace<'_>) -> Result<Command, CommandParseError> {
|
||||||
|
const USAGE: CommandParseError = CommandParseError::Usage(CommandType::Connect);
|
||||||
|
let pos = args.next().ok_or(USAGE)?.parse().map_err(|_| USAGE)?;
|
||||||
|
let host = args.next().ok_or(USAGE)?.to_string();
|
||||||
|
let port = args.next().and_then(|p| p.parse().ok());
|
||||||
|
Ok(Command::Connect(pos, host, port))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_disconnect_cmd(mut args: SplitWhitespace<'_>) -> Result<Command, CommandParseError> {
|
||||||
|
const USAGE: CommandParseError = CommandParseError::Usage(CommandType::Disconnect);
|
||||||
|
let id = args.next().ok_or(USAGE)?.parse().map_err(|_| USAGE)?;
|
||||||
|
Ok(Command::Disconnect(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_activate_cmd(mut args: SplitWhitespace<'_>) -> Result<Command, CommandParseError> {
|
||||||
|
const USAGE: CommandParseError = CommandParseError::Usage(CommandType::Activate);
|
||||||
|
let id = args.next().ok_or(USAGE)?.parse().map_err(|_| USAGE)?;
|
||||||
|
Ok(Command::Activate(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_deactivate_cmd(mut args: SplitWhitespace<'_>) -> Result<Command, CommandParseError> {
|
||||||
|
const USAGE: CommandParseError = CommandParseError::Usage(CommandType::Deactivate);
|
||||||
|
let id = args.next().ok_or(USAGE)?.parse().map_err(|_| USAGE)?;
|
||||||
|
Ok(Command::Deactivate(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_set_host(mut args: SplitWhitespace<'_>) -> Result<Command, CommandParseError> {
|
||||||
|
const USAGE: CommandParseError = CommandParseError::Usage(CommandType::SetHost);
|
||||||
|
let id = args.next().ok_or(USAGE)?.parse().map_err(|_| USAGE)?;
|
||||||
|
let host = args.next().ok_or(USAGE)?.parse().map_err(|_| USAGE)?;
|
||||||
|
Ok(Command::SetHost(id, host))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_set_port(mut args: SplitWhitespace<'_>) -> Result<Command, CommandParseError> {
|
||||||
|
const USAGE: CommandParseError = CommandParseError::Usage(CommandType::SetPort);
|
||||||
|
let id = args.next().ok_or(USAGE)?.parse().map_err(|_| USAGE)?;
|
||||||
|
let port = args.next().and_then(|p| p.parse().ok());
|
||||||
|
Ok(Command::SetPort(id, port))
|
||||||
|
}
|
||||||
@@ -1,167 +1,298 @@
|
|||||||
use clap::{Args, Parser, Subcommand};
|
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
|
use tokio::{
|
||||||
use std::{net::IpAddr, time::Duration};
|
io::{AsyncBufReadExt, BufReader},
|
||||||
use thiserror::Error;
|
task::LocalSet,
|
||||||
|
|
||||||
use lan_mouse_ipc::{
|
|
||||||
connect_async, ClientHandle, ConnectionError, FrontendEvent, FrontendRequest, IpcError,
|
|
||||||
Position,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
use std::io::{self, Write};
|
||||||
pub enum CliError {
|
|
||||||
/// is the service running?
|
|
||||||
#[error("could not connect: `{0}` - is the service running?")]
|
|
||||||
ServiceNotRunning(#[from] ConnectionError),
|
|
||||||
#[error("error communicating with service: {0}")]
|
|
||||||
Ipc(#[from] IpcError),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Parser, Debug, PartialEq, Eq)]
|
use self::command::{Command, CommandType};
|
||||||
#[command(name = "lan-mouse-cli", about = "LanMouse CLI interface")]
|
|
||||||
pub struct CliArgs {
|
|
||||||
#[command(subcommand)]
|
|
||||||
command: CliSubcommand,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Args, Clone, Debug, PartialEq, Eq)]
|
use lan_mouse_ipc::{
|
||||||
struct Client {
|
AsyncFrontendEventReader, AsyncFrontendRequestWriter, ClientConfig, ClientHandle, ClientState,
|
||||||
#[arg(long)]
|
FrontendEvent, FrontendRequest, IpcError, DEFAULT_PORT,
|
||||||
hostname: Option<String>,
|
};
|
||||||
#[arg(long)]
|
|
||||||
port: Option<u16>,
|
|
||||||
#[arg(long)]
|
|
||||||
ips: Option<Vec<IpAddr>>,
|
|
||||||
#[arg(long)]
|
|
||||||
enter_hook: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Subcommand, Debug, PartialEq, Eq)]
|
mod command;
|
||||||
enum CliSubcommand {
|
|
||||||
/// add a new client
|
|
||||||
AddClient(Client),
|
|
||||||
/// remove an existing client
|
|
||||||
RemoveClient { id: ClientHandle },
|
|
||||||
/// activate a client
|
|
||||||
Activate { id: ClientHandle },
|
|
||||||
/// deactivate a client
|
|
||||||
Deactivate { id: ClientHandle },
|
|
||||||
/// list configured clients
|
|
||||||
List,
|
|
||||||
/// change hostname
|
|
||||||
SetHost {
|
|
||||||
id: ClientHandle,
|
|
||||||
host: Option<String>,
|
|
||||||
},
|
|
||||||
/// change port
|
|
||||||
SetPort { id: ClientHandle, port: u16 },
|
|
||||||
/// set position
|
|
||||||
SetPosition { id: ClientHandle, pos: Position },
|
|
||||||
/// set ips
|
|
||||||
SetIps { id: ClientHandle, ips: Vec<IpAddr> },
|
|
||||||
/// re-enable capture
|
|
||||||
EnableCapture,
|
|
||||||
/// re-enable emulation
|
|
||||||
EnableEmulation,
|
|
||||||
/// authorize a public key
|
|
||||||
AuthorizeKey {
|
|
||||||
description: String,
|
|
||||||
sha256_fingerprint: String,
|
|
||||||
},
|
|
||||||
/// deauthorize a public key
|
|
||||||
RemoveAuthorizedKey { sha256_fingerprint: String },
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn run(args: CliArgs) -> Result<(), CliError> {
|
pub fn run() -> Result<(), IpcError> {
|
||||||
execute(args.command).await?;
|
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||||
|
.enable_io()
|
||||||
|
.enable_time()
|
||||||
|
.build()?;
|
||||||
|
runtime.block_on(LocalSet::new().run_until(async move {
|
||||||
|
let (rx, tx) = lan_mouse_ipc::connect_async().await?;
|
||||||
|
let mut cli = Cli::new(rx, tx);
|
||||||
|
cli.run().await
|
||||||
|
}))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn execute(cmd: CliSubcommand) -> Result<(), CliError> {
|
struct Cli {
|
||||||
let (mut rx, mut tx) = connect_async(Some(Duration::from_millis(500))).await?;
|
clients: Vec<(ClientHandle, ClientConfig, ClientState)>,
|
||||||
match cmd {
|
rx: AsyncFrontendEventReader,
|
||||||
CliSubcommand::AddClient(Client {
|
tx: AsyncFrontendRequestWriter,
|
||||||
hostname,
|
}
|
||||||
port,
|
|
||||||
ips,
|
impl Cli {
|
||||||
enter_hook,
|
fn new(rx: AsyncFrontendEventReader, tx: AsyncFrontendRequestWriter) -> Cli {
|
||||||
}) => {
|
Self {
|
||||||
tx.request(FrontendRequest::Create).await?;
|
clients: vec![],
|
||||||
while let Some(e) = rx.next().await {
|
rx,
|
||||||
if let FrontendEvent::Created(handle, _, _) = e? {
|
tx,
|
||||||
if let Some(hostname) = hostname {
|
|
||||||
tx.request(FrontendRequest::UpdateHostname(handle, Some(hostname)))
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
if let Some(port) = port {
|
|
||||||
tx.request(FrontendRequest::UpdatePort(handle, port))
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
if let Some(ips) = ips {
|
|
||||||
tx.request(FrontendRequest::UpdateFixIps(handle, ips))
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
if let Some(enter_hook) = enter_hook {
|
|
||||||
tx.request(FrontendRequest::UpdateEnterHook(handle, Some(enter_hook)))
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
CliSubcommand::RemoveClient { id } => tx.request(FrontendRequest::Delete(id)).await?,
|
|
||||||
CliSubcommand::Activate { id } => tx.request(FrontendRequest::Activate(id, true)).await?,
|
|
||||||
CliSubcommand::Deactivate { id } => {
|
|
||||||
tx.request(FrontendRequest::Activate(id, false)).await?
|
|
||||||
}
|
|
||||||
CliSubcommand::List => {
|
|
||||||
tx.request(FrontendRequest::Enumerate()).await?;
|
|
||||||
while let Some(e) = rx.next().await {
|
|
||||||
if let FrontendEvent::Enumerate(clients) = e? {
|
|
||||||
for (handle, config, state) in clients {
|
|
||||||
let host = config.hostname.unwrap_or("unknown".to_owned());
|
|
||||||
let port = config.port;
|
|
||||||
let pos = config.pos;
|
|
||||||
let active = state.active;
|
|
||||||
let ips = state.ips;
|
|
||||||
println!(
|
|
||||||
"id {handle}: {host}:{port} ({pos}) active: {active}, ips: {ips:?}"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
CliSubcommand::SetHost { id, host } => {
|
|
||||||
tx.request(FrontendRequest::UpdateHostname(id, host))
|
|
||||||
.await?
|
|
||||||
}
|
|
||||||
CliSubcommand::SetPort { id, port } => {
|
|
||||||
tx.request(FrontendRequest::UpdatePort(id, port)).await?
|
|
||||||
}
|
|
||||||
CliSubcommand::SetPosition { id, pos } => {
|
|
||||||
tx.request(FrontendRequest::UpdatePosition(id, pos)).await?
|
|
||||||
}
|
|
||||||
CliSubcommand::SetIps { id, ips } => {
|
|
||||||
tx.request(FrontendRequest::UpdateFixIps(id, ips)).await?
|
|
||||||
}
|
|
||||||
CliSubcommand::EnableCapture => tx.request(FrontendRequest::EnableCapture).await?,
|
|
||||||
CliSubcommand::EnableEmulation => tx.request(FrontendRequest::EnableEmulation).await?,
|
|
||||||
CliSubcommand::AuthorizeKey {
|
|
||||||
description,
|
|
||||||
sha256_fingerprint,
|
|
||||||
} => {
|
|
||||||
tx.request(FrontendRequest::AuthorizeKey(
|
|
||||||
description,
|
|
||||||
sha256_fingerprint,
|
|
||||||
))
|
|
||||||
.await?
|
|
||||||
}
|
|
||||||
CliSubcommand::RemoveAuthorizedKey { sha256_fingerprint } => {
|
|
||||||
tx.request(FrontendRequest::RemoveAuthorizedKey(sha256_fingerprint))
|
|
||||||
.await?
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn run(&mut self) -> Result<(), IpcError> {
|
||||||
|
let stdin = tokio::io::stdin();
|
||||||
|
let stdin = BufReader::new(stdin);
|
||||||
|
let mut stdin = stdin.lines();
|
||||||
|
|
||||||
|
/* initial state sync */
|
||||||
|
self.clients = loop {
|
||||||
|
match self.rx.next().await {
|
||||||
|
Some(Ok(e)) => {
|
||||||
|
if let FrontendEvent::Enumerate(clients) = e {
|
||||||
|
break clients;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(Err(e)) => return Err(e),
|
||||||
|
None => return Ok(()),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
loop {
|
||||||
|
prompt()?;
|
||||||
|
tokio::select! {
|
||||||
|
line = stdin.next_line() => {
|
||||||
|
let Some(line) = line? else {
|
||||||
|
break Ok(());
|
||||||
|
};
|
||||||
|
let cmd: Command = match line.parse() {
|
||||||
|
Ok(cmd) => cmd,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("{e}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
self.execute(cmd).await?;
|
||||||
|
}
|
||||||
|
event = self.rx.next() => {
|
||||||
|
if let Some(event) = event {
|
||||||
|
self.handle_event(event?);
|
||||||
|
} else {
|
||||||
|
break Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn execute(&mut self, cmd: Command) -> Result<(), IpcError> {
|
||||||
|
match cmd {
|
||||||
|
Command::None => {}
|
||||||
|
Command::Connect(pos, host, port) => {
|
||||||
|
let request = FrontendRequest::Create;
|
||||||
|
self.tx.request(request).await?;
|
||||||
|
let handle = loop {
|
||||||
|
if let Some(Ok(event)) = self.rx.next().await {
|
||||||
|
match event {
|
||||||
|
FrontendEvent::Created(h, c, s) => {
|
||||||
|
self.clients.push((h, c, s));
|
||||||
|
break h;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
self.handle_event(event);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
for request in [
|
||||||
|
FrontendRequest::UpdateHostname(handle, Some(host.clone())),
|
||||||
|
FrontendRequest::UpdatePort(handle, port.unwrap_or(DEFAULT_PORT)),
|
||||||
|
FrontendRequest::UpdatePosition(handle, pos),
|
||||||
|
] {
|
||||||
|
self.tx.request(request).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Command::Disconnect(id) => {
|
||||||
|
self.tx.request(FrontendRequest::Delete(id)).await?;
|
||||||
|
loop {
|
||||||
|
if let Some(Ok(event)) = self.rx.next().await {
|
||||||
|
self.handle_event(event.clone());
|
||||||
|
if let FrontendEvent::Deleted(_) = event {
|
||||||
|
self.handle_event(event);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Command::Activate(id) => {
|
||||||
|
self.tx.request(FrontendRequest::Activate(id, true)).await?;
|
||||||
|
}
|
||||||
|
Command::Deactivate(id) => {
|
||||||
|
self.tx
|
||||||
|
.request(FrontendRequest::Activate(id, false))
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
Command::List => {
|
||||||
|
self.tx.request(FrontendRequest::Enumerate()).await?;
|
||||||
|
while let Some(e) = self.rx.next().await {
|
||||||
|
let event = e?;
|
||||||
|
self.handle_event(event.clone());
|
||||||
|
if let FrontendEvent::Enumerate(_) = event {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Command::SetHost(handle, host) => {
|
||||||
|
let request = FrontendRequest::UpdateHostname(handle, Some(host.clone()));
|
||||||
|
self.tx.request(request).await?;
|
||||||
|
}
|
||||||
|
Command::SetPort(handle, port) => {
|
||||||
|
let request = FrontendRequest::UpdatePort(handle, port.unwrap_or(DEFAULT_PORT));
|
||||||
|
self.tx.request(request).await?;
|
||||||
|
}
|
||||||
|
Command::Help => {
|
||||||
|
for cmd_type in [
|
||||||
|
CommandType::List,
|
||||||
|
CommandType::Connect,
|
||||||
|
CommandType::Disconnect,
|
||||||
|
CommandType::Activate,
|
||||||
|
CommandType::Deactivate,
|
||||||
|
CommandType::SetHost,
|
||||||
|
CommandType::SetPort,
|
||||||
|
] {
|
||||||
|
eprintln!("{}", cmd_type.usage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_mut(
|
||||||
|
&mut self,
|
||||||
|
handle: ClientHandle,
|
||||||
|
) -> Option<&mut (ClientHandle, ClientConfig, ClientState)> {
|
||||||
|
self.clients.iter_mut().find(|(h, _, _)| *h == handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove(
|
||||||
|
&mut self,
|
||||||
|
handle: ClientHandle,
|
||||||
|
) -> Option<(ClientHandle, ClientConfig, ClientState)> {
|
||||||
|
let idx = self.clients.iter().position(|(h, _, _)| *h == handle);
|
||||||
|
idx.map(|i| self.clients.swap_remove(i))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_event(&mut self, event: FrontendEvent) {
|
||||||
|
match event {
|
||||||
|
FrontendEvent::Created(h, c, s) => {
|
||||||
|
eprint!("client added ({h}): ");
|
||||||
|
print_config(&c);
|
||||||
|
eprint!(" ");
|
||||||
|
print_state(&s);
|
||||||
|
eprintln!();
|
||||||
|
self.clients.push((h, c, s));
|
||||||
|
}
|
||||||
|
FrontendEvent::NoSuchClient(h) => {
|
||||||
|
eprintln!("no such client: {h}");
|
||||||
|
}
|
||||||
|
FrontendEvent::State(h, c, s) => {
|
||||||
|
if let Some((_, config, state)) = self.find_mut(h) {
|
||||||
|
let old_host = config.hostname.clone().unwrap_or("\"\"".into());
|
||||||
|
let new_host = c.hostname.clone().unwrap_or("\"\"".into());
|
||||||
|
if old_host != new_host {
|
||||||
|
eprintln!(
|
||||||
|
"client {h}: hostname updated ({} -> {})",
|
||||||
|
old_host, new_host
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if config.port != c.port {
|
||||||
|
eprintln!("client {h} changed port: {} -> {}", config.port, c.port);
|
||||||
|
}
|
||||||
|
if config.fix_ips != c.fix_ips {
|
||||||
|
eprintln!("client {h} ips updated: {:?}", c.fix_ips)
|
||||||
|
}
|
||||||
|
*config = c;
|
||||||
|
if state.active ^ s.active {
|
||||||
|
eprintln!(
|
||||||
|
"client {h} {}",
|
||||||
|
if s.active { "activated" } else { "deactivated" }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
*state = s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FrontendEvent::Deleted(h) => {
|
||||||
|
if let Some((h, c, _)) = self.remove(h) {
|
||||||
|
eprint!("client {h} removed (");
|
||||||
|
print_config(&c);
|
||||||
|
eprintln!(")");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FrontendEvent::PortChanged(p, e) => {
|
||||||
|
if let Some(e) = e {
|
||||||
|
eprintln!("failed to change port: {e}");
|
||||||
|
} else {
|
||||||
|
eprintln!("changed port to {p}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FrontendEvent::Enumerate(clients) => {
|
||||||
|
self.clients = clients;
|
||||||
|
self.print_clients();
|
||||||
|
}
|
||||||
|
FrontendEvent::Error(e) => {
|
||||||
|
eprintln!("ERROR: {e}");
|
||||||
|
}
|
||||||
|
FrontendEvent::CaptureStatus(s) => {
|
||||||
|
eprintln!("capture status: {s:?}")
|
||||||
|
}
|
||||||
|
FrontendEvent::EmulationStatus(s) => {
|
||||||
|
eprintln!("emulation status: {s:?}")
|
||||||
|
}
|
||||||
|
FrontendEvent::AuthorizedUpdated(fingerprints) => {
|
||||||
|
eprintln!("authorized keys changed:");
|
||||||
|
for (desc, fp) in fingerprints {
|
||||||
|
eprintln!("{desc}: {fp}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FrontendEvent::PublicKeyFingerprint(fp) => {
|
||||||
|
eprintln!("the public key fingerprint of this device is {fp}");
|
||||||
|
}
|
||||||
|
FrontendEvent::IncomingConnected(..) => {}
|
||||||
|
FrontendEvent::IncomingDisconnected(..) => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_clients(&mut self) {
|
||||||
|
for (h, c, s) in self.clients.iter() {
|
||||||
|
eprint!("client {h}: ");
|
||||||
|
print_config(c);
|
||||||
|
eprint!(" ");
|
||||||
|
print_state(s);
|
||||||
|
eprintln!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prompt() -> io::Result<()> {
|
||||||
|
eprint!("lan-mouse > ");
|
||||||
|
std::io::stderr().flush()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn print_config(c: &ClientConfig) {
|
||||||
|
eprint!(
|
||||||
|
"{}:{} ({}), ips: {:?}",
|
||||||
|
c.hostname.clone().unwrap_or("(no hostname)".into()),
|
||||||
|
c.port,
|
||||||
|
c.pos,
|
||||||
|
c.fix_ips
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_state(s: &ClientState) {
|
||||||
|
eprint!("active: {}, dns: {:?}", s.active, s.ips);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use crate::{ConnectionError, FrontendEvent, FrontendRequest, IpcError};
|
use crate::{ConnectionError, FrontendEvent, FrontendRequest, IpcError};
|
||||||
use std::{
|
use std::{
|
||||||
cmp::min,
|
cmp::min,
|
||||||
|
io,
|
||||||
task::{ready, Poll},
|
task::{ready, Poll},
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
@@ -46,7 +47,7 @@ impl Stream for AsyncFrontendEventReader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl AsyncFrontendRequestWriter {
|
impl AsyncFrontendRequestWriter {
|
||||||
pub async fn request(&mut self, request: FrontendRequest) -> Result<(), IpcError> {
|
pub async fn request(&mut self, request: FrontendRequest) -> Result<(), io::Error> {
|
||||||
let mut json = serde_json::to_string(&request).unwrap();
|
let mut json = serde_json::to_string(&request).unwrap();
|
||||||
log::debug!("requesting: {json}");
|
log::debug!("requesting: {json}");
|
||||||
json.push('\n');
|
json.push('\n');
|
||||||
@@ -56,16 +57,8 @@ impl AsyncFrontendRequestWriter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn connect_async(
|
pub async fn connect_async(
|
||||||
timeout: Option<Duration>,
|
|
||||||
) -> Result<(AsyncFrontendEventReader, AsyncFrontendRequestWriter), ConnectionError> {
|
) -> Result<(AsyncFrontendEventReader, AsyncFrontendRequestWriter), ConnectionError> {
|
||||||
let stream = if let Some(duration) = timeout {
|
let stream = wait_for_service().await?;
|
||||||
tokio::select! {
|
|
||||||
s = wait_for_service() => s?,
|
|
||||||
_ = tokio::time::sleep(duration) => return Err(ConnectionError::Timeout),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
wait_for_service().await?
|
|
||||||
};
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
let (rx, tx): (ReadHalf<UnixStream>, WriteHalf<UnixStream>) = tokio::io::split(stream);
|
let (rx, tx): (ReadHalf<UnixStream>, WriteHalf<UnixStream>) = tokio::io::split(stream);
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
|
|||||||
@@ -30,8 +30,6 @@ pub enum ConnectionError {
|
|||||||
SocketPath(#[from] SocketPathError),
|
SocketPath(#[from] SocketPathError),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Io(#[from] io::Error),
|
Io(#[from] io::Error),
|
||||||
#[error("connection timed out")]
|
|
||||||
Timeout,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
@@ -239,8 +237,6 @@ pub enum FrontendRequest {
|
|||||||
AuthorizeKey(String, String),
|
AuthorizeKey(String, String),
|
||||||
/// remove fingerprint (fingerprint)
|
/// remove fingerprint (fingerprint)
|
||||||
RemoveAuthorizedKey(String),
|
RemoveAuthorizedKey(String),
|
||||||
/// change the hook command
|
|
||||||
UpdateEnterHook(u64, Option<String>),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default, Serialize, Deserialize)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default, Serialize, Deserialize)]
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ in {
|
|||||||
};
|
};
|
||||||
Service = {
|
Service = {
|
||||||
Type = "simple";
|
Type = "simple";
|
||||||
ExecStart = "${cfg.package}/bin/lan-mouse daemon";
|
ExecStart = "${cfg.package}/bin/lan-mouse --daemon";
|
||||||
};
|
};
|
||||||
Install.WantedBy = [
|
Install.WantedBy = [
|
||||||
(lib.mkIf config.wayland.windowManager.hyprland.systemd.enable "hyprland-session.target")
|
(lib.mkIf config.wayland.windowManager.hyprland.systemd.enable "hyprland-session.target")
|
||||||
@@ -65,7 +65,7 @@ in {
|
|||||||
config = {
|
config = {
|
||||||
ProgramArguments = [
|
ProgramArguments = [
|
||||||
"${cfg.package}/bin/lan-mouse"
|
"${cfg.package}/bin/lan-mouse"
|
||||||
"daemon"
|
"--daemon"
|
||||||
];
|
];
|
||||||
KeepAlive = true;
|
KeepAlive = true;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ After=graphical-session.target
|
|||||||
BindsTo=graphical-session.target
|
BindsTo=graphical-session.target
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
ExecStart=/usr/bin/lan-mouse daemon
|
ExecStart=/usr/bin/lan-mouse --daemon
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
|
|||||||
@@ -1,13 +1,9 @@
|
|||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use clap::Args;
|
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use input_capture::{self, CaptureError, CaptureEvent, InputCapture, InputCaptureError, Position};
|
use input_capture::{self, CaptureError, CaptureEvent, InputCapture, InputCaptureError, Position};
|
||||||
use input_event::{Event, KeyboardEvent};
|
use input_event::{Event, KeyboardEvent};
|
||||||
|
|
||||||
#[derive(Args, Debug, Eq, PartialEq)]
|
pub async fn run(config: Config) -> Result<(), InputCaptureError> {
|
||||||
pub struct TestCaptureArgs {}
|
|
||||||
|
|
||||||
pub async fn run(config: Config, _args: TestCaptureArgs) -> Result<(), InputCaptureError> {
|
|
||||||
log::info!("running input capture test");
|
log::info!("running input capture test");
|
||||||
log::info!("creating input capture");
|
log::info!("creating input capture");
|
||||||
let backend = config.capture_backend.map(|b| b.into());
|
let backend = config.capture_backend.map(|b| b.into());
|
||||||
|
|||||||
@@ -199,13 +199,6 @@ impl ClientManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// update the enter hook command of the client
|
|
||||||
pub(crate) fn set_enter_hook(&self, handle: ClientHandle, enter_hook: Option<String>) {
|
|
||||||
if let Some((c, _s)) = self.clients.borrow_mut().get_mut(handle as usize) {
|
|
||||||
c.cmd = enter_hook;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// set resolving status of the client
|
/// set resolving status of the client
|
||||||
pub(crate) fn set_resolving(&self, handle: ClientHandle, status: bool) {
|
pub(crate) fn set_resolving(&self, handle: ClientHandle, status: bool) {
|
||||||
if let Some((_, s)) = self.clients.borrow_mut().get_mut(handle as usize) {
|
if let Some((_, s)) = self.clients.borrow_mut().get_mut(handle as usize) {
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
use crate::capture_test::TestCaptureArgs;
|
use clap::{Parser, ValueEnum};
|
||||||
use crate::emulation_test::TestEmulationArgs;
|
|
||||||
use clap::{Parser, Subcommand, ValueEnum};
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::env::{self, VarError};
|
use std::env::{self, VarError};
|
||||||
@@ -12,7 +10,6 @@ use std::{collections::HashSet, io};
|
|||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use toml;
|
use toml;
|
||||||
|
|
||||||
use lan_mouse_cli::CliArgs;
|
|
||||||
use lan_mouse_ipc::{Position, DEFAULT_PORT};
|
use lan_mouse_ipc::{Position, DEFAULT_PORT};
|
||||||
|
|
||||||
use input_event::scancode::{
|
use input_event::scancode::{
|
||||||
@@ -58,7 +55,7 @@ impl ConfigToml {
|
|||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[command(author, version=build::CLAP_LONG_VERSION, about, long_about = None)]
|
#[command(author, version=build::CLAP_LONG_VERSION, about, long_about = None)]
|
||||||
pub struct Args {
|
struct CliArgs {
|
||||||
/// the listen port for lan-mouse
|
/// the listen port for lan-mouse
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
port: Option<u16>,
|
port: Option<u16>,
|
||||||
@@ -69,34 +66,31 @@ pub struct Args {
|
|||||||
|
|
||||||
/// non-default config file location
|
/// non-default config file location
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
pub config: Option<PathBuf>,
|
config: Option<String>,
|
||||||
|
|
||||||
#[command(subcommand)]
|
/// run only the service as a daemon without the frontend
|
||||||
pub command: Option<Command>,
|
#[arg(short, long)]
|
||||||
|
daemon: bool,
|
||||||
|
|
||||||
|
/// test input capture
|
||||||
|
#[arg(long)]
|
||||||
|
test_capture: bool,
|
||||||
|
|
||||||
|
/// test input emulation
|
||||||
|
#[arg(long)]
|
||||||
|
test_emulation: bool,
|
||||||
|
|
||||||
/// capture backend override
|
/// capture backend override
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
pub capture_backend: Option<CaptureBackend>,
|
capture_backend: Option<CaptureBackend>,
|
||||||
|
|
||||||
/// emulation backend override
|
/// emulation backend override
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
pub emulation_backend: Option<EmulationBackend>,
|
emulation_backend: Option<EmulationBackend>,
|
||||||
|
|
||||||
/// path to non-default certificate location
|
/// path to non-default certificate location
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
pub cert_path: Option<PathBuf>,
|
cert_path: Option<PathBuf>,
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Subcommand, Debug, Eq, PartialEq)]
|
|
||||||
pub enum Command {
|
|
||||||
/// test input emulation
|
|
||||||
TestEmulation(TestEmulationArgs),
|
|
||||||
/// test input capture
|
|
||||||
TestCapture(TestCaptureArgs),
|
|
||||||
/// Lan Mouse commandline interface
|
|
||||||
Cli(CliArgs),
|
|
||||||
/// run in daemon mode
|
|
||||||
Daemon,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, ValueEnum)]
|
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, ValueEnum)]
|
||||||
@@ -224,8 +218,8 @@ impl Display for EmulationBackend {
|
|||||||
pub enum Frontend {
|
pub enum Frontend {
|
||||||
#[serde(rename = "gtk")]
|
#[serde(rename = "gtk")]
|
||||||
Gtk,
|
Gtk,
|
||||||
#[serde(rename = "none")]
|
#[serde(rename = "cli")]
|
||||||
None,
|
Cli,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Frontend {
|
impl Default for Frontend {
|
||||||
@@ -233,7 +227,7 @@ impl Default for Frontend {
|
|||||||
if cfg!(feature = "gtk") {
|
if cfg!(feature = "gtk") {
|
||||||
Self::Gtk
|
Self::Gtk
|
||||||
} else {
|
} else {
|
||||||
Self::None
|
Self::Cli
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -254,6 +248,8 @@ pub struct Config {
|
|||||||
pub port: u16,
|
pub port: u16,
|
||||||
/// list of clients
|
/// list of clients
|
||||||
pub clients: Vec<(TomlClient, Position)>,
|
pub clients: Vec<(TomlClient, Position)>,
|
||||||
|
/// whether or not to run as a daemon
|
||||||
|
pub daemon: bool,
|
||||||
/// configured release bind
|
/// configured release bind
|
||||||
pub release_bind: Vec<scancode::Linux>,
|
pub release_bind: Vec<scancode::Linux>,
|
||||||
/// test capture instead of running the app
|
/// test capture instead of running the app
|
||||||
@@ -287,7 +283,8 @@ const DEFAULT_RELEASE_KEYS: [scancode::Linux; 4] =
|
|||||||
[KeyLeftCtrl, KeyLeftShift, KeyLeftMeta, KeyLeftAlt];
|
[KeyLeftCtrl, KeyLeftShift, KeyLeftMeta, KeyLeftAlt];
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
pub fn new(args: &Args) -> Result<Self, ConfigError> {
|
pub fn new() -> Result<Self, ConfigError> {
|
||||||
|
let args = CliArgs::parse();
|
||||||
const CONFIG_FILE_NAME: &str = "config.toml";
|
const CONFIG_FILE_NAME: &str = "config.toml";
|
||||||
const CERT_FILE_NAME: &str = "lan-mouse.pem";
|
const CERT_FILE_NAME: &str = "lan-mouse.pem";
|
||||||
|
|
||||||
@@ -309,7 +306,7 @@ impl Config {
|
|||||||
let config_file = config_path.join(CONFIG_FILE_NAME);
|
let config_file = config_path.join(CONFIG_FILE_NAME);
|
||||||
|
|
||||||
// --config <file> overrules default location
|
// --config <file> overrules default location
|
||||||
let config_file = args.config.clone().unwrap_or(config_file);
|
let config_file = args.config.map(PathBuf::from).unwrap_or(config_file);
|
||||||
|
|
||||||
let mut config_toml = match ConfigToml::new(&config_file) {
|
let mut config_toml = match ConfigToml::new(&config_file) {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@@ -345,7 +342,6 @@ impl Config {
|
|||||||
|
|
||||||
let cert_path = args
|
let cert_path = args
|
||||||
.cert_path
|
.cert_path
|
||||||
.clone()
|
|
||||||
.or(config_toml.as_ref().and_then(|c| c.cert_path.clone()))
|
.or(config_toml.as_ref().and_then(|c| c.cert_path.clone()))
|
||||||
.unwrap_or(config_path.join(CERT_FILE_NAME));
|
.unwrap_or(config_path.join(CERT_FILE_NAME));
|
||||||
|
|
||||||
@@ -371,14 +367,16 @@ impl Config {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let test_capture = matches!(args.command, Some(Command::TestCapture(_)));
|
let daemon = args.daemon;
|
||||||
let test_emulation = matches!(args.command, Some(Command::TestEmulation(_)));
|
let test_capture = args.test_capture;
|
||||||
|
let test_emulation = args.test_emulation;
|
||||||
|
|
||||||
Ok(Config {
|
Ok(Config {
|
||||||
path: config_path,
|
path: config_path,
|
||||||
authorized_fingerprints,
|
authorized_fingerprints,
|
||||||
capture_backend,
|
capture_backend,
|
||||||
emulation_backend,
|
emulation_backend,
|
||||||
|
daemon,
|
||||||
frontend,
|
frontend,
|
||||||
clients,
|
clients,
|
||||||
port,
|
port,
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use clap::Args;
|
|
||||||
use input_emulation::{InputEmulation, InputEmulationError};
|
use input_emulation::{InputEmulation, InputEmulationError};
|
||||||
use input_event::{Event, PointerEvent};
|
use input_event::{Event, PointerEvent};
|
||||||
use std::f64::consts::PI;
|
use std::f64::consts::PI;
|
||||||
@@ -8,17 +7,7 @@ use std::time::{Duration, Instant};
|
|||||||
const FREQUENCY_HZ: f64 = 1.0;
|
const FREQUENCY_HZ: f64 = 1.0;
|
||||||
const RADIUS: f64 = 100.0;
|
const RADIUS: f64 = 100.0;
|
||||||
|
|
||||||
#[derive(Args, Debug, Eq, PartialEq)]
|
pub async fn run(config: Config) -> Result<(), InputEmulationError> {
|
||||||
pub struct TestEmulationArgs {
|
|
||||||
#[arg(long)]
|
|
||||||
mouse: bool,
|
|
||||||
#[arg(long)]
|
|
||||||
keyboard: bool,
|
|
||||||
#[arg(long)]
|
|
||||||
scroll: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn run(config: Config, _args: TestEmulationArgs) -> Result<(), InputEmulationError> {
|
|
||||||
log::info!("running input emulation test");
|
log::info!("running input emulation test");
|
||||||
|
|
||||||
let backend = config.emulation_backend.map(|b| b.into());
|
let backend = config.emulation_backend.map(|b| b.into());
|
||||||
|
|||||||
71
src/main.rs
71
src/main.rs
@@ -1,14 +1,12 @@
|
|||||||
use clap::Parser;
|
|
||||||
use env_logger::Env;
|
use env_logger::Env;
|
||||||
use input_capture::InputCaptureError;
|
use input_capture::InputCaptureError;
|
||||||
use input_emulation::InputEmulationError;
|
use input_emulation::InputEmulationError;
|
||||||
use lan_mouse::{
|
use lan_mouse::{
|
||||||
capture_test,
|
capture_test,
|
||||||
config::{self, Config, ConfigError, Frontend},
|
config::{Config, ConfigError, Frontend},
|
||||||
emulation_test,
|
emulation_test,
|
||||||
service::{Service, ServiceError},
|
service::{Service, ServiceError},
|
||||||
};
|
};
|
||||||
use lan_mouse_cli::CliError;
|
|
||||||
use lan_mouse_ipc::{IpcError, IpcListenerCreationError};
|
use lan_mouse_ipc::{IpcError, IpcListenerCreationError};
|
||||||
use std::{
|
use std::{
|
||||||
future::Future,
|
future::Future,
|
||||||
@@ -32,8 +30,6 @@ enum LanMouseError {
|
|||||||
Capture(#[from] InputCaptureError),
|
Capture(#[from] InputCaptureError),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Emulation(#[from] InputEmulationError),
|
Emulation(#[from] InputEmulationError),
|
||||||
#[error(transparent)]
|
|
||||||
Cli(#[from] CliError),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
@@ -49,39 +45,34 @@ fn main() {
|
|||||||
|
|
||||||
fn run() -> Result<(), LanMouseError> {
|
fn run() -> Result<(), LanMouseError> {
|
||||||
// parse config file + cli args
|
// parse config file + cli args
|
||||||
let args = config::Args::parse();
|
let config = Config::new()?;
|
||||||
let config = config::Config::new(&args)?;
|
if config.test_capture {
|
||||||
match args.command {
|
run_async(capture_test::run(config))?;
|
||||||
Some(command) => match command {
|
} else if config.test_emulation {
|
||||||
config::Command::TestEmulation(args) => run_async(emulation_test::run(config, args))?,
|
run_async(emulation_test::run(config))?;
|
||||||
config::Command::TestCapture(args) => run_async(capture_test::run(config, args))?,
|
} else if config.daemon {
|
||||||
config::Command::Cli(cli_args) => run_async(lan_mouse_cli::run(cli_args))?,
|
// if daemon is specified we run the service
|
||||||
config::Command::Daemon => {
|
match run_async(run_service(config)) {
|
||||||
// if daemon is specified we run the service
|
Err(LanMouseError::Service(ServiceError::IpcListen(
|
||||||
match run_async(run_service(config)) {
|
IpcListenerCreationError::AlreadyRunning,
|
||||||
Err(LanMouseError::Service(ServiceError::IpcListen(
|
))) => log::info!("service already running!"),
|
||||||
IpcListenerCreationError::AlreadyRunning,
|
r => r?,
|
||||||
))) => log::info!("service already running!"),
|
|
||||||
r => r?,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
None => {
|
|
||||||
// otherwise start the service as a child process and
|
|
||||||
// run a frontend
|
|
||||||
let mut service = start_service()?;
|
|
||||||
run_frontend(&config)?;
|
|
||||||
#[cfg(unix)]
|
|
||||||
{
|
|
||||||
// on unix we give the service a chance to terminate gracefully
|
|
||||||
let pid = service.id() as libc::pid_t;
|
|
||||||
unsafe {
|
|
||||||
libc::kill(pid, libc::SIGINT);
|
|
||||||
}
|
|
||||||
service.wait()?;
|
|
||||||
}
|
|
||||||
service.kill()?;
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// otherwise start the service as a child process and
|
||||||
|
// run a frontend
|
||||||
|
let mut service = start_service()?;
|
||||||
|
run_frontend(&config)?;
|
||||||
|
#[cfg(unix)]
|
||||||
|
{
|
||||||
|
// on unix we give the service a chance to terminate gracefully
|
||||||
|
let pid = service.id() as libc::pid_t;
|
||||||
|
unsafe {
|
||||||
|
libc::kill(pid, libc::SIGINT);
|
||||||
|
}
|
||||||
|
service.wait()?;
|
||||||
|
}
|
||||||
|
service.kill()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -105,7 +96,7 @@ where
|
|||||||
fn start_service() -> Result<Child, io::Error> {
|
fn start_service() -> Result<Child, io::Error> {
|
||||||
let child = Command::new(std::env::current_exe()?)
|
let child = Command::new(std::env::current_exe()?)
|
||||||
.args(std::env::args().skip(1))
|
.args(std::env::args().skip(1))
|
||||||
.arg("daemon")
|
.arg("--daemon")
|
||||||
.spawn()?;
|
.spawn()?;
|
||||||
Ok(child)
|
Ok(child)
|
||||||
}
|
}
|
||||||
@@ -127,8 +118,8 @@ fn run_frontend(config: &Config) -> Result<(), IpcError> {
|
|||||||
}
|
}
|
||||||
#[cfg(not(feature = "gtk"))]
|
#[cfg(not(feature = "gtk"))]
|
||||||
Frontend::Gtk => panic!("gtk frontend requested but feature not enabled!"),
|
Frontend::Gtk => panic!("gtk frontend requested but feature not enabled!"),
|
||||||
Frontend::None => {
|
Frontend::Cli => {
|
||||||
log::warn!("no frontend available!");
|
lan_mouse_cli::run()?;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -193,9 +193,6 @@ impl Service {
|
|||||||
FrontendRequest::ResolveDns(handle) => self.resolve(handle),
|
FrontendRequest::ResolveDns(handle) => self.resolve(handle),
|
||||||
FrontendRequest::Sync => self.sync_frontend(),
|
FrontendRequest::Sync => self.sync_frontend(),
|
||||||
FrontendRequest::RemoveAuthorizedKey(key) => self.remove_authorized_key(key),
|
FrontendRequest::RemoveAuthorizedKey(key) => self.remove_authorized_key(key),
|
||||||
FrontendRequest::UpdateEnterHook(handle, enter_hook) => {
|
|
||||||
self.update_enter_hook(handle, enter_hook)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -479,11 +476,6 @@ impl Service {
|
|||||||
self.broadcast_client(handle);
|
self.broadcast_client(handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_enter_hook(&mut self, handle: ClientHandle, enter_hook: Option<String>) {
|
|
||||||
self.client_manager.set_enter_hook(handle, enter_hook);
|
|
||||||
self.broadcast_client(handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn broadcast_client(&mut self, handle: ClientHandle) {
|
fn broadcast_client(&mut self, handle: ClientHandle) {
|
||||||
let event = self
|
let event = self
|
||||||
.client_manager
|
.client_manager
|
||||||
|
|||||||
Reference in New Issue
Block a user