mirror of
https://github.com/feschber/lan-mouse.git
synced 2026-04-01 01:20:55 +03:00
improve capture error handling
This commit is contained in:
@@ -6,6 +6,8 @@ use futures_core::Stream;
|
|||||||
|
|
||||||
use input_event::Event;
|
use input_event::Event;
|
||||||
|
|
||||||
|
use crate::CaptureError;
|
||||||
|
|
||||||
use super::{CaptureHandle, InputCapture, Position};
|
use super::{CaptureHandle, InputCapture, Position};
|
||||||
|
|
||||||
pub struct DummyInputCapture {}
|
pub struct DummyInputCapture {}
|
||||||
@@ -37,7 +39,7 @@ impl InputCapture for DummyInputCapture {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Stream for DummyInputCapture {
|
impl Stream for DummyInputCapture {
|
||||||
type Item = io::Result<(CaptureHandle, Event)>;
|
type Item = Result<(CaptureHandle, Event), CaptureError>;
|
||||||
|
|
||||||
fn poll_next(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
fn poll_next(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||||
Poll::Pending
|
Poll::Pending
|
||||||
|
|||||||
@@ -92,7 +92,9 @@ impl Display for Backend {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait InputCapture: Stream<Item = io::Result<(CaptureHandle, Event)>> + Unpin {
|
pub trait InputCapture:
|
||||||
|
Stream<Item = Result<(CaptureHandle, Event), CaptureError>> + Unpin
|
||||||
|
{
|
||||||
/// create a new client with the given id
|
/// create a new client with the given id
|
||||||
fn create(&mut self, id: CaptureHandle, pos: Position) -> io::Result<()>;
|
fn create(&mut self, id: CaptureHandle, pos: Position) -> io::Result<()>;
|
||||||
|
|
||||||
@@ -105,8 +107,10 @@ pub trait InputCapture: Stream<Item = io::Result<(CaptureHandle, Event)>> + Unpi
|
|||||||
|
|
||||||
pub async fn create_backend(
|
pub async fn create_backend(
|
||||||
backend: Backend,
|
backend: Backend,
|
||||||
) -> Result<Box<dyn InputCapture<Item = io::Result<(CaptureHandle, Event)>>>, CaptureCreationError>
|
) -> Result<
|
||||||
{
|
Box<dyn InputCapture<Item = Result<(CaptureHandle, Event), CaptureError>>>,
|
||||||
|
CaptureCreationError,
|
||||||
|
> {
|
||||||
match backend {
|
match backend {
|
||||||
#[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
|
#[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
|
||||||
Backend::InputCapturePortal => Ok(Box::new(libei::LibeiInputCapture::new().await?)),
|
Backend::InputCapturePortal => Ok(Box::new(libei::LibeiInputCapture::new().await?)),
|
||||||
@@ -124,8 +128,10 @@ pub async fn create_backend(
|
|||||||
|
|
||||||
pub async fn create(
|
pub async fn create(
|
||||||
backend: Option<Backend>,
|
backend: Option<Backend>,
|
||||||
) -> Result<Box<dyn InputCapture<Item = io::Result<(CaptureHandle, Event)>>>, CaptureCreationError>
|
) -> Result<
|
||||||
{
|
Box<dyn InputCapture<Item = Result<(CaptureHandle, Event), CaptureError>>>,
|
||||||
|
CaptureCreationError,
|
||||||
|
> {
|
||||||
if let Some(backend) = backend {
|
if let Some(backend) = backend {
|
||||||
let b = create_backend(backend).await;
|
let b = create_backend(backend).await;
|
||||||
if b.is_ok() {
|
if b.is_ok() {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use ashpd::{
|
|||||||
},
|
},
|
||||||
enumflags2::BitFlags,
|
enumflags2::BitFlags,
|
||||||
};
|
};
|
||||||
use futures::StreamExt;
|
use futures::{FutureExt, StreamExt};
|
||||||
use reis::{
|
use reis::{
|
||||||
ei::{self, keyboard::KeyState},
|
ei::{self, keyboard::KeyState},
|
||||||
eis::button::ButtonState,
|
eis::button::ButtonState,
|
||||||
@@ -17,9 +17,9 @@ use std::{
|
|||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
io,
|
io,
|
||||||
os::unix::net::UnixStream,
|
os::unix::net::UnixStream,
|
||||||
pin::Pin,
|
pin::{pin, Pin},
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
task::{ready, Context, Poll},
|
task::{Context, Poll},
|
||||||
};
|
};
|
||||||
use tokio::{
|
use tokio::{
|
||||||
sync::mpsc::{Receiver, Sender},
|
sync::mpsc::{Receiver, Sender},
|
||||||
@@ -192,7 +192,7 @@ async fn libei_event_handler(
|
|||||||
.map_err(ReisConvertEventStreamError::from)?;
|
.map_err(ReisConvertEventStreamError::from)?;
|
||||||
log::trace!("from ei: {ei_event:?}");
|
log::trace!("from ei: {ei_event:?}");
|
||||||
let client = current_client.get();
|
let client = current_client.get();
|
||||||
handle_ei_event(ei_event, client, &context, &event_tx).await;
|
handle_ei_event(ei_event, client, &context, &event_tx).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -252,10 +252,9 @@ async fn do_capture<'a>(
|
|||||||
if active_clients.is_empty() {
|
if active_clients.is_empty() {
|
||||||
wait_for_active_client(&mut notify_rx, &mut active_clients).await;
|
wait_for_active_client(&mut notify_rx, &mut active_clients).await;
|
||||||
if notify_rx.is_closed() {
|
if notify_rx.is_closed() {
|
||||||
break Ok(());
|
break;
|
||||||
} else {
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let current_client = Rc::new(Cell::new(None));
|
let current_client = Rc::new(Cell::new(None));
|
||||||
@@ -270,13 +269,12 @@ async fn do_capture<'a>(
|
|||||||
let (context, ei_event_stream) = connect_to_eis(input_capture, &session).await?;
|
let (context, ei_event_stream) = connect_to_eis(input_capture, &session).await?;
|
||||||
|
|
||||||
// async event task
|
// async event task
|
||||||
let mut ei_task: JoinHandle<Result<(), CaptureError>> =
|
let mut ei_task = pin!(libei_event_handler(
|
||||||
tokio::task::spawn_local(libei_event_handler(
|
ei_event_stream,
|
||||||
ei_event_stream,
|
context,
|
||||||
context,
|
event_tx.clone(),
|
||||||
event_tx.clone(),
|
current_client.clone(),
|
||||||
current_client.clone(),
|
));
|
||||||
));
|
|
||||||
|
|
||||||
let mut activated = input_capture.receive_activated().await?;
|
let mut activated = input_capture.receive_activated().await?;
|
||||||
let mut zones_changed = input_capture.receive_zones_changed().await?;
|
let mut zones_changed = input_capture.receive_zones_changed().await?;
|
||||||
@@ -320,10 +318,8 @@ async fn do_capture<'a>(
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
res = &mut ei_task => {
|
res = &mut ei_task => {
|
||||||
if let Err(e) = res.expect("ei task paniced") {
|
/* propagate errors to toplevel task */
|
||||||
log::warn!("libei task exited: {e}");
|
res?;
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
release_capture(
|
release_capture(
|
||||||
@@ -342,19 +338,16 @@ async fn do_capture<'a>(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
res = &mut ei_task => {
|
res = &mut ei_task => {
|
||||||
if let Err(e) = res.expect("ei task paniced") {
|
res?;
|
||||||
log::warn!("libei task exited: {e}");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ei_task.abort();
|
|
||||||
input_capture.disable(&session).await?;
|
input_capture.disable(&session).await?;
|
||||||
if event_tx.is_closed() {
|
if event_tx.is_closed() {
|
||||||
break Ok(());
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn release_capture(
|
async fn release_capture(
|
||||||
@@ -410,7 +403,7 @@ async fn handle_ei_event(
|
|||||||
current_client: Option<CaptureHandle>,
|
current_client: Option<CaptureHandle>,
|
||||||
context: &ei::Context,
|
context: &ei::Context,
|
||||||
event_tx: &Sender<(CaptureHandle, Event)>,
|
event_tx: &Sender<(CaptureHandle, Event)>,
|
||||||
) {
|
) -> Result<(), CaptureError> {
|
||||||
match ei_event {
|
match ei_event {
|
||||||
EiEvent::SeatAdded(s) => {
|
EiEvent::SeatAdded(s) => {
|
||||||
s.seat.bind_capabilities(&[
|
s.seat.bind_capabilities(&[
|
||||||
@@ -421,7 +414,7 @@ async fn handle_ei_event(
|
|||||||
DeviceCapability::Scroll,
|
DeviceCapability::Scroll,
|
||||||
DeviceCapability::Button,
|
DeviceCapability::Button,
|
||||||
]);
|
]);
|
||||||
context.flush().unwrap();
|
context.flush().map_err(|e| io::Error::new(e.kind(), e))?;
|
||||||
}
|
}
|
||||||
EiEvent::SeatRemoved(_) => {}
|
EiEvent::SeatRemoved(_) => {}
|
||||||
EiEvent::DeviceAdded(_) => {}
|
EiEvent::DeviceAdded(_) => {}
|
||||||
@@ -439,7 +432,7 @@ async fn handle_ei_event(
|
|||||||
event_tx
|
event_tx
|
||||||
.send((current_client, Event::Keyboard(modifier_event)))
|
.send((current_client, Event::Keyboard(modifier_event)))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.map_err(|_| CaptureError::EndOfStream)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
EiEvent::Frame(_) => {}
|
EiEvent::Frame(_) => {}
|
||||||
@@ -459,7 +452,7 @@ async fn handle_ei_event(
|
|||||||
event_tx
|
event_tx
|
||||||
.send((current_client, Event::Pointer(motion_event)))
|
.send((current_client, Event::Pointer(motion_event)))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.map_err(|_| CaptureError::EndOfStream)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
EiEvent::PointerMotionAbsolute(_) => {}
|
EiEvent::PointerMotionAbsolute(_) => {}
|
||||||
@@ -476,7 +469,7 @@ async fn handle_ei_event(
|
|||||||
event_tx
|
event_tx
|
||||||
.send((current_client, Event::Pointer(button_event)))
|
.send((current_client, Event::Pointer(button_event)))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.map_err(|_| CaptureError::EndOfStream)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
EiEvent::ScrollDelta(delta) => {
|
EiEvent::ScrollDelta(delta) => {
|
||||||
@@ -500,7 +493,7 @@ async fn handle_ei_event(
|
|||||||
event_tx
|
event_tx
|
||||||
.send((handle, Event::Pointer(event)))
|
.send((handle, Event::Pointer(event)))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.map_err(|_| CaptureError::EndOfStream)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -516,7 +509,7 @@ async fn handle_ei_event(
|
|||||||
event_tx
|
event_tx
|
||||||
.send((current_client, Event::Pointer(event)))
|
.send((current_client, Event::Pointer(event)))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.map_err(|_| CaptureError::EndOfStream)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if scroll.discrete_dx != 0 {
|
if scroll.discrete_dx != 0 {
|
||||||
@@ -528,7 +521,7 @@ async fn handle_ei_event(
|
|||||||
event_tx
|
event_tx
|
||||||
.send((current_client, Event::Pointer(event)))
|
.send((current_client, Event::Pointer(event)))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.map_err(|_| CaptureError::EndOfStream)?;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -545,7 +538,7 @@ async fn handle_ei_event(
|
|||||||
event_tx
|
event_tx
|
||||||
.send((current_client, Event::Keyboard(key_event)))
|
.send((current_client, Event::Keyboard(key_event)))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.map_err(|_| CaptureError::EndOfStream)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
EiEvent::TouchDown(_) => {}
|
EiEvent::TouchDown(_) => {}
|
||||||
@@ -555,6 +548,7 @@ async fn handle_ei_event(
|
|||||||
log::error!("disconnect: {d:?}");
|
log::error!("disconnect: {d:?}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> LanMouseInputCapture for LibeiInputCapture<'a> {
|
impl<'a> LanMouseInputCapture for LibeiInputCapture<'a> {
|
||||||
@@ -584,12 +578,15 @@ impl<'a> LanMouseInputCapture for LibeiInputCapture<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Stream for LibeiInputCapture<'a> {
|
impl<'a> Stream for LibeiInputCapture<'a> {
|
||||||
type Item = io::Result<(CaptureHandle, Event)>;
|
type Item = Result<(CaptureHandle, Event), CaptureError>;
|
||||||
|
|
||||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||||
match ready!(self.event_rx.poll_recv(cx)) {
|
match self.libei_task.poll_unpin(cx) {
|
||||||
None => Poll::Ready(None),
|
Poll::Ready(r) => match r.expect("failed to join") {
|
||||||
Some(e) => Poll::Ready(Some(Ok(e))),
|
Ok(()) => Poll::Ready(None),
|
||||||
|
Err(e) => Poll::Ready(Some(Err(e))),
|
||||||
|
},
|
||||||
|
Poll::Pending => self.event_rx.poll_recv(cx).map(|e| e.map(Result::Ok)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
use crate::{error::MacOSInputCaptureCreationError, CaptureHandle, InputCapture, Position};
|
use crate::{
|
||||||
|
error::MacOSInputCaptureCreationError, CaptureError, CaptureHandle, InputCapture, Position,
|
||||||
|
};
|
||||||
use futures_core::Stream;
|
use futures_core::Stream;
|
||||||
use input_event::Event;
|
use input_event::Event;
|
||||||
use std::task::{Context, Poll};
|
use std::task::{Context, Poll};
|
||||||
@@ -13,7 +15,7 @@ impl MacOSInputCapture {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Stream for MacOSInputCapture {
|
impl Stream for MacOSInputCapture {
|
||||||
type Item = io::Result<(CaptureHandle, Event)>;
|
type Item = Result<(CaptureHandle, Event), CaptureError>;
|
||||||
|
|
||||||
fn poll_next(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
fn poll_next(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||||
Poll::Pending
|
Poll::Pending
|
||||||
|
|||||||
@@ -62,6 +62,8 @@ use tempfile;
|
|||||||
|
|
||||||
use input_event::{Event, KeyboardEvent, PointerEvent};
|
use input_event::{Event, KeyboardEvent, PointerEvent};
|
||||||
|
|
||||||
|
use crate::CaptureError;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
error::{LayerShellCaptureCreationError, WaylandBindError},
|
error::{LayerShellCaptureCreationError, WaylandBindError},
|
||||||
CaptureHandle, InputCapture, Position,
|
CaptureHandle, InputCapture, Position,
|
||||||
@@ -582,7 +584,7 @@ impl InputCapture for WaylandInputCapture {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Stream for WaylandInputCapture {
|
impl Stream for WaylandInputCapture {
|
||||||
type Item = io::Result<(CaptureHandle, Event)>;
|
type Item = Result<(CaptureHandle, Event), CaptureError>;
|
||||||
|
|
||||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||||
if let Some(event) = self.0.get_mut().state.pending_events.pop_front() {
|
if let Some(event) = self.0.get_mut().state.pending_events.pop_front() {
|
||||||
@@ -600,7 +602,7 @@ impl Stream for WaylandInputCapture {
|
|||||||
// prepare next read
|
// prepare next read
|
||||||
match inner.prepare_read() {
|
match inner.prepare_read() {
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(e) => return Poll::Ready(Some(Err(e))),
|
Err(e) => return Poll::Ready(Some(Err(e.into()))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -610,14 +612,14 @@ impl Stream for WaylandInputCapture {
|
|||||||
// flush outgoing events
|
// flush outgoing events
|
||||||
if let Err(e) = inner.flush_events() {
|
if let Err(e) = inner.flush_events() {
|
||||||
if e.kind() != ErrorKind::WouldBlock {
|
if e.kind() != ErrorKind::WouldBlock {
|
||||||
return Poll::Ready(Some(Err(e)));
|
return Poll::Ready(Some(Err(e.into())));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// prepare for the next read
|
// prepare for the next read
|
||||||
match inner.prepare_read() {
|
match inner.prepare_read() {
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(e) => return Poll::Ready(Some(Err(e))),
|
Err(e) => return Poll::Ready(Some(Err(e.into()))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ use input_event::{
|
|||||||
Event, KeyboardEvent, PointerEvent, BTN_BACK, BTN_FORWARD, BTN_LEFT, BTN_MIDDLE, BTN_RIGHT,
|
Event, KeyboardEvent, PointerEvent, BTN_BACK, BTN_FORWARD, BTN_LEFT, BTN_MIDDLE, BTN_RIGHT,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{CaptureHandle, InputCapture, Position};
|
use super::{CaptureError, CaptureHandle, InputCapture, Position};
|
||||||
|
|
||||||
enum Request {
|
enum Request {
|
||||||
Create(CaptureHandle, Position),
|
Create(CaptureHandle, Position),
|
||||||
@@ -609,7 +609,7 @@ impl WindowsInputCapture {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Stream for WindowsInputCapture {
|
impl Stream for WindowsInputCapture {
|
||||||
type Item = io::Result<(CaptureHandle, Event)>;
|
type Item = Result<(CaptureHandle, Event), CaptureError>;
|
||||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||||
match ready!(self.event_rx.poll_recv(cx)) {
|
match ready!(self.event_rx.poll_recv(cx)) {
|
||||||
None => Poll::Ready(None),
|
None => Poll::Ready(None),
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ use std::task::Poll;
|
|||||||
|
|
||||||
use futures_core::Stream;
|
use futures_core::Stream;
|
||||||
|
|
||||||
|
use crate::CaptureError;
|
||||||
|
|
||||||
use super::InputCapture;
|
use super::InputCapture;
|
||||||
use input_event::Event;
|
use input_event::Event;
|
||||||
|
|
||||||
@@ -32,7 +34,7 @@ impl InputCapture for X11InputCapture {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Stream for X11InputCapture {
|
impl Stream for X11InputCapture {
|
||||||
type Item = io::Result<(CaptureHandle, Event)>;
|
type Item = Result<(CaptureHandle, Event), CaptureError>;
|
||||||
|
|
||||||
fn poll_next(
|
fn poll_next(
|
||||||
self: std::pin::Pin<&mut Self>,
|
self: std::pin::Pin<&mut Self>,
|
||||||
|
|||||||
@@ -1,167 +1,186 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<interface>
|
<interface>
|
||||||
<requires lib="gtk" version="4.0"/>
|
<requires lib="gtk" version="4.0"/>
|
||||||
<requires lib="libadwaita" version="1.0"/>
|
<requires lib="libadwaita" version="1.0"/>
|
||||||
<menu id="main-menu">
|
<menu id="main-menu">
|
||||||
<item>
|
<item>
|
||||||
<attribute name="label" translatable="yes">_Close window</attribute>
|
<attribute name="label" translatable="yes">_Close window</attribute>
|
||||||
<attribute name="action">window.close</attribute>
|
<attribute name="action">window.close</attribute>
|
||||||
</item>
|
</item>
|
||||||
</menu>
|
</menu>
|
||||||
<template class="LanMouseWindow" parent="AdwApplicationWindow">
|
<template class="LanMouseWindow" parent="AdwApplicationWindow">
|
||||||
<property name="width-request">600</property>
|
<property name="width-request">600</property>
|
||||||
<property name="height-request">700</property>
|
<property name="height-request">700</property>
|
||||||
<property name="title" translatable="yes">Lan Mouse</property>
|
<property name="title" translatable="yes">Lan Mouse</property>
|
||||||
<property name="show-menubar">True</property>
|
<property name="show-menubar">True</property>
|
||||||
<property name="content">
|
<property name="content">
|
||||||
<object class="GtkBox">
|
<object class="GtkBox">
|
||||||
<property name="orientation">vertical</property>
|
<property name="orientation">vertical</property>
|
||||||
<child type="top">
|
<child type="top">
|
||||||
<object class="AdwHeaderBar">
|
<object class="AdwHeaderBar">
|
||||||
<child type ="end">
|
<child type ="end">
|
||||||
<object class="GtkMenuButton">
|
<object class="GtkMenuButton">
|
||||||
<property name="icon-name">open-menu-symbolic</property>
|
<property name="icon-name">open-menu-symbolic</property>
|
||||||
<property name="menu-model">main-menu</property>
|
<property name="menu-model">main-menu</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<style>
|
<style>
|
||||||
<class name="flat"/>
|
<class name="flat"/>
|
||||||
</style>
|
</style>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="AdwToastOverlay" id="toast_overlay">
|
<object class="AdwToastOverlay" id="toast_overlay">
|
||||||
<child>
|
<child>
|
||||||
<object class="AdwStatusPage">
|
<object class="AdwStatusPage">
|
||||||
<property name="title" translatable="yes">Lan Mouse</property>
|
<property name="title" translatable="yes">Lan Mouse</property>
|
||||||
<property name="description" translatable="yes">easily use your mouse and keyboard on multiple computers</property>
|
<property name="description" translatable="yes">easily use your mouse and keyboard on multiple computers</property>
|
||||||
<property name="icon-name">de.feschber.LanMouse</property>
|
<property name="icon-name">de.feschber.LanMouse</property>
|
||||||
<property name="child">
|
<property name="child">
|
||||||
<object class="AdwClamp">
|
<object class="AdwClamp">
|
||||||
<property name="maximum-size">600</property>
|
<property name="maximum-size">600</property>
|
||||||
<property name="tightening-threshold">0</property>
|
<property name="tightening-threshold">0</property>
|
||||||
<property name="child">
|
<property name="child">
|
||||||
<object class="GtkBox">
|
<object class="GtkBox">
|
||||||
<property name="orientation">vertical</property>
|
<property name="orientation">vertical</property>
|
||||||
<property name="spacing">12</property>
|
<property name="spacing">12</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="AdwPreferencesGroup">
|
<object class="AdwPreferencesGroup">
|
||||||
<property name="title" translatable="yes">General</property>
|
<property name="title" translatable="yes">General</property>
|
||||||
<!--
|
<!--
|
||||||
<child>
|
<child>
|
||||||
<object class="AdwActionRow">
|
<object class="AdwActionRow">
|
||||||
<property name="title" translatable="yes">enable</property>
|
<property name="title" translatable="yes">enable</property>
|
||||||
<child type="suffix">
|
<child type="suffix">
|
||||||
<object class="GtkSwitch">
|
<object class="GtkSwitch">
|
||||||
<property name="valign">center</property>
|
<property name="valign">center</property>
|
||||||
<property name="tooltip-text" translatable="yes">enable</property>
|
<property name="tooltip-text" translatable="yes">enable</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
-->
|
-->
|
||||||
<child>
|
<child>
|
||||||
<object class="AdwActionRow">
|
<object class="AdwActionRow">
|
||||||
<property name="title">port</property>
|
<property name="title">port</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkEntry" id="port_entry">
|
<object class="GtkEntry" id="port_entry">
|
||||||
<signal name="activate" handler="handle_port_edit_apply" swapped="true"/>
|
<signal name="activate" handler="handle_port_edit_apply" swapped="true"/>
|
||||||
<signal name="changed" handler="handle_port_changed" swapped="true"/>
|
<signal name="changed" handler="handle_port_changed" swapped="true"/>
|
||||||
<!-- <signal name="delete-text" handler="handle_port_changed" swapped="true"/> -->
|
<!-- <signal name="delete-text" handler="handle_port_changed" swapped="true"/> -->
|
||||||
<!-- <property name="title" translatable="yes">port</property> -->
|
<!-- <property name="title" translatable="yes">port</property> -->
|
||||||
<property name="placeholder-text">4242</property>
|
<property name="placeholder-text">4242</property>
|
||||||
<property name="width-chars">5</property>
|
<property name="width-chars">5</property>
|
||||||
<property name="xalign">0.5</property>
|
<property name="xalign">0.5</property>
|
||||||
<property name="valign">center</property>
|
<property name="valign">center</property>
|
||||||
<!-- <property name="show-apply-button">True</property> -->
|
<!-- <property name="show-apply-button">True</property> -->
|
||||||
<property name="input-purpose">GTK_INPUT_PURPOSE_DIGITS</property>
|
<property name="input-purpose">GTK_INPUT_PURPOSE_DIGITS</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkButton" id="port_edit_apply">
|
<object class="GtkButton" id="port_edit_apply">
|
||||||
<signal name="clicked" handler="handle_port_edit_apply" swapped="true"/>
|
<signal name="clicked" handler="handle_port_edit_apply" swapped="true"/>
|
||||||
<property name="icon-name">object-select-symbolic</property>
|
<property name="icon-name">object-select-symbolic</property>
|
||||||
<property name="valign">center</property>
|
<property name="valign">center</property>
|
||||||
<property name="visible">false</property>
|
<property name="visible">false</property>
|
||||||
<property name="name">port-edit-apply</property>
|
<property name="name">port-edit-apply</property>
|
||||||
<style><class name="success"/></style>
|
<style><class name="success"/></style>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkButton" id="port_edit_cancel">
|
<object class="GtkButton" id="port_edit_cancel">
|
||||||
<signal name="clicked" handler="handle_port_edit_cancel" swapped="true"/>
|
<signal name="clicked" handler="handle_port_edit_cancel" swapped="true"/>
|
||||||
<property name="icon-name">process-stop-symbolic</property>
|
<property name="icon-name">process-stop-symbolic</property>
|
||||||
<property name="valign">center</property>
|
<property name="valign">center</property>
|
||||||
<property name="visible">false</property>
|
<property name="visible">false</property>
|
||||||
<property name="name">port-edit-cancel</property>
|
<property name="name">port-edit-cancel</property>
|
||||||
<style><class name="error"/></style>
|
<style><class name="error"/></style>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="AdwActionRow">
|
<object class="AdwActionRow">
|
||||||
<property name="title">hostname</property>
|
<property name="title">hostname</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkLabel" id="hostname_label">
|
<object class="GtkLabel" id="hostname_label">
|
||||||
<property name="label"><span font_style="italic" font_weight="light" foreground="darkgrey">could not determine hostname</span></property>
|
<property name="label"><span font_style="italic" font_weight="light" foreground="darkgrey">could not determine hostname</span></property>
|
||||||
<property name="use-markup">true</property>
|
<property name="use-markup">true</property>
|
||||||
<property name="valign">center</property>
|
<property name="valign">center</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkButton" id="copy-hostname-button">
|
<object class="GtkButton" id="copy-hostname-button">
|
||||||
<property name="icon-name">edit-copy-symbolic</property>
|
<property name="icon-name">edit-copy-symbolic</property>
|
||||||
<property name="valign">center</property>
|
<property name="valign">center</property>
|
||||||
<signal name="clicked" handler="handle_copy_hostname" swapped="true"/>
|
<signal name="clicked" handler="handle_copy_hostname" swapped="true"/>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
<child>
|
||||||
</child>
|
<object class="AdwActionRow">
|
||||||
<child>
|
<property name="title">capture / emulation status</property>
|
||||||
<object class="AdwPreferencesGroup">
|
<child>
|
||||||
<property name="title" translatable="yes">Connections</property>
|
<object class="GtkButton" id="input_emulation_button">
|
||||||
<property name="header-suffix">
|
<property name="icon-name">input-mouse-symbolic</property>
|
||||||
<object class="GtkButton">
|
<property name="valign">center</property>
|
||||||
<signal name="clicked" handler="handle_add_client_pressed" swapped="true"/>
|
<signal name="clicked" handler="handle_emulation" swapped="true"/>
|
||||||
<property name="child">
|
</object>
|
||||||
<object class="AdwButtonContent">
|
</child>
|
||||||
<property name="icon-name">list-add-symbolic</property>
|
<child>
|
||||||
<property name="label" translatable="yes">Add</property>
|
<object class="GtkButton" id="input_capture_button">
|
||||||
</object>
|
<property name="icon-name">input-mouse-symbolic</property>
|
||||||
</property>
|
<property name="valign">center</property>
|
||||||
<style>
|
<signal name="clicked" handler="handle_capture" swapped="true"/>
|
||||||
<class name="flat"/>
|
</object>
|
||||||
</style>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
</property>
|
</child>
|
||||||
<child>
|
</object>
|
||||||
<object class="GtkListBox" id="client_list">
|
</child>
|
||||||
<property name="selection-mode">none</property>
|
<child>
|
||||||
<child type="placeholder">
|
<object class="AdwPreferencesGroup">
|
||||||
<object class="AdwActionRow" id="client_placeholder">
|
<property name="title" translatable="yes">Connections</property>
|
||||||
<property name="title">No connections!</property>
|
<property name="header-suffix">
|
||||||
<property name="subtitle">add a new client via the + button</property>
|
<object class="GtkButton">
|
||||||
</object>
|
<signal name="clicked" handler="handle_add_client_pressed" swapped="true"/>
|
||||||
</child>
|
<property name="child">
|
||||||
<style>
|
<object class="AdwButtonContent">
|
||||||
<class name="boxed-list" />
|
<property name="icon-name">list-add-symbolic</property>
|
||||||
</style>
|
<property name="label" translatable="yes">Add</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</property>
|
||||||
</object>
|
<style>
|
||||||
</child>
|
<class name="flat"/>
|
||||||
</object>
|
</style>
|
||||||
</property>
|
</object>
|
||||||
</object>
|
</property>
|
||||||
</property>
|
<child>
|
||||||
</object>
|
<object class="GtkListBox" id="client_list">
|
||||||
</child>
|
<property name="selection-mode">none</property>
|
||||||
</object>
|
<child type="placeholder">
|
||||||
</child>
|
<object class="AdwActionRow" id="client_placeholder">
|
||||||
</object>
|
<property name="title">No connections!</property>
|
||||||
</property>
|
<property name="subtitle">add a new client via the + button</property>
|
||||||
</template>
|
</object>
|
||||||
|
</child>
|
||||||
|
<style>
|
||||||
|
<class name="boxed-list" />
|
||||||
|
</style>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</property>
|
||||||
|
</object>
|
||||||
|
</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</property>
|
||||||
|
</template>
|
||||||
</interface>
|
</interface>
|
||||||
|
|||||||
@@ -109,6 +109,10 @@ pub enum FrontendRequest {
|
|||||||
UpdateFixIps(ClientHandle, Vec<IpAddr>),
|
UpdateFixIps(ClientHandle, Vec<IpAddr>),
|
||||||
/// request the state of the given client
|
/// request the state of the given client
|
||||||
GetState(ClientHandle),
|
GetState(ClientHandle),
|
||||||
|
/// request reenabling input capture
|
||||||
|
EnableCapture,
|
||||||
|
/// request reenabling input emulation
|
||||||
|
EnableEmulation,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
|||||||
@@ -215,6 +215,13 @@ impl Window {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn request_capture(&self) {
|
||||||
|
self.request(FrontendRequest::EnableCapture);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn request_emulation(&self) {
|
||||||
|
self.request(FrontendRequest::EnableEmulation);
|
||||||
|
}
|
||||||
pub fn request_client_state(&self, client: &ClientObject) {
|
pub fn request_client_state(&self, client: &ClientObject) {
|
||||||
let handle = client.handle();
|
let handle = client.handle();
|
||||||
let event = FrontendRequest::GetState(handle);
|
let event = FrontendRequest::GetState(handle);
|
||||||
|
|||||||
@@ -30,6 +30,10 @@ pub struct Window {
|
|||||||
pub hostname_label: TemplateChild<Label>,
|
pub hostname_label: TemplateChild<Label>,
|
||||||
#[template_child]
|
#[template_child]
|
||||||
pub toast_overlay: TemplateChild<ToastOverlay>,
|
pub toast_overlay: TemplateChild<ToastOverlay>,
|
||||||
|
#[template_child]
|
||||||
|
pub input_emulation_button: TemplateChild<Button>,
|
||||||
|
#[template_child]
|
||||||
|
pub input_capture_button: TemplateChild<Button>,
|
||||||
pub clients: RefCell<Option<gio::ListStore>>,
|
pub clients: RefCell<Option<gio::ListStore>>,
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
pub stream: RefCell<Option<UnixStream>>,
|
pub stream: RefCell<Option<UnixStream>>,
|
||||||
@@ -100,6 +104,17 @@ impl Window {
|
|||||||
self.port_edit_cancel.set_visible(false);
|
self.port_edit_cancel.set_visible(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[template_callback]
|
||||||
|
fn handle_emulation(&self) {
|
||||||
|
self.obj().request_emulation();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[template_callback]
|
||||||
|
fn handle_capture(&self) {
|
||||||
|
log::info!("requesting capture");
|
||||||
|
self.obj().request_capture();
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_port(&self, port: u16) {
|
pub fn set_port(&self, port: u16) {
|
||||||
self.port.set(port);
|
self.port.set(port);
|
||||||
if port == DEFAULT_PORT {
|
if port == DEFAULT_PORT {
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ pub enum CaptureEvent {
|
|||||||
Destroy(CaptureHandle),
|
Destroy(CaptureHandle),
|
||||||
/// termination signal
|
/// termination signal
|
||||||
Terminate,
|
Terminate,
|
||||||
|
/// restart input capture
|
||||||
|
Restart,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(
|
pub fn new(
|
||||||
@@ -55,6 +57,13 @@ pub fn new(
|
|||||||
}
|
}
|
||||||
CaptureEvent::Create(h, p) => capture.create(h, p)?,
|
CaptureEvent::Create(h, p) => capture.create(h, p)?,
|
||||||
CaptureEvent::Destroy(h) => capture.destroy(h)?,
|
CaptureEvent::Destroy(h) => capture.destroy(h)?,
|
||||||
|
CaptureEvent::Restart => {
|
||||||
|
let clients = server.client_manager.borrow().get_client_states().map(|(h, (c,_))| (h, c.pos)).collect::<Vec<_>>();
|
||||||
|
capture = input_capture::create(backend).await?;
|
||||||
|
for (handle, pos) in clients {
|
||||||
|
capture.create(handle, pos.into())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
CaptureEvent::Terminate => break,
|
CaptureEvent::Terminate => break,
|
||||||
},
|
},
|
||||||
None => break,
|
None => break,
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ pub enum EmulationEvent {
|
|||||||
ReleaseKeys(ClientHandle),
|
ReleaseKeys(ClientHandle),
|
||||||
/// termination signal
|
/// termination signal
|
||||||
Terminate,
|
Terminate,
|
||||||
|
/// restart input emulation
|
||||||
|
Restart,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(
|
pub fn new(
|
||||||
@@ -90,6 +92,13 @@ async fn emulation_task(
|
|||||||
EmulationEvent::Create(h) => emulation.create(h).await,
|
EmulationEvent::Create(h) => emulation.create(h).await,
|
||||||
EmulationEvent::Destroy(h) => emulation.destroy(h).await,
|
EmulationEvent::Destroy(h) => emulation.destroy(h).await,
|
||||||
EmulationEvent::ReleaseKeys(c) => release_keys(&server, &mut emulation, c).await?,
|
EmulationEvent::ReleaseKeys(c) => release_keys(&server, &mut emulation, c).await?,
|
||||||
|
EmulationEvent::Restart => {
|
||||||
|
let clients = server.client_manager.borrow().get_client_states().map(|(h, _)| h).collect::<Vec<_>>();
|
||||||
|
emulation = input_emulation::create(backend).await?;
|
||||||
|
for handle in clients {
|
||||||
|
emulation.create(handle).await;
|
||||||
|
}
|
||||||
|
},
|
||||||
EmulationEvent::Terminate => break,
|
EmulationEvent::Terminate => break,
|
||||||
},
|
},
|
||||||
None => break,
|
None => break,
|
||||||
|
|||||||
@@ -106,6 +106,12 @@ async fn handle_frontend_event(
|
|||||||
) -> bool {
|
) -> bool {
|
||||||
log::debug!("frontend: {event:?}");
|
log::debug!("frontend: {event:?}");
|
||||||
match event {
|
match event {
|
||||||
|
FrontendRequest::EnableCapture => {
|
||||||
|
let _ = capture.send(CaptureEvent::Restart).await;
|
||||||
|
}
|
||||||
|
FrontendRequest::EnableEmulation => {
|
||||||
|
let _ = emulate.send(EmulationEvent::Restart).await;
|
||||||
|
}
|
||||||
FrontendRequest::Create => {
|
FrontendRequest::Create => {
|
||||||
let handle = add_client(server, frontend).await;
|
let handle = add_client(server, frontend).await;
|
||||||
resolve_dns(server, resolve_tx, handle).await;
|
resolve_dns(server, resolve_tx, handle).await;
|
||||||
|
|||||||
Reference in New Issue
Block a user