port changing functionality (#34)

* port changing functionality

* add portchange to cli frontend
This commit is contained in:
Ferdinand Schober
2023-10-17 15:12:17 +02:00
committed by GitHub
parent 60a73b3cb0
commit e88241e816
9 changed files with 263 additions and 121 deletions

View File

@@ -1,3 +1,11 @@
#delete-button {
color: @red_1;
}
#port-edit-cancel {
color: @red_1;
}
#port-edit-apply {
color: @green_1;
}

View File

@@ -1,3 +1,11 @@
#delete-button {
color: @red_3;
}
#port-edit-cancel {
color: @red_3;
}
#port-edit-apply {
color: @green_3;
}

View File

@@ -8,114 +8,143 @@
<attribute name="action">window.close</attribute>
</item>
</menu>
<template class="LanMouseWindow" parent="GtkApplicationWindow">
<template class="LanMouseWindow" parent="AdwApplicationWindow">
<property name="width-request">600</property>
<property name="title" translatable="yes">Lan Mouse</property>
<property name="show-menubar">True</property>
<child type="titlebar">
<object class="GtkHeaderBar">
<child type ="end">
<object class="GtkMenuButton">
<property name="icon-name">open-menu-symbolic</property>
<property name="menu-model">main-menu</property>
<property name="content">
<object class="AdwToolbarView">
<child type="top">
<object class="AdwHeaderBar">
<child type ="end">
<object class="GtkMenuButton">
<property name="icon-name">open-menu-symbolic</property>
<property name="menu-model">main-menu</property>
</object>
</child>
<style>
<class name="flat"/>
</style>
</object>
</child>
<style>
<class name="flat"/>
</style>
</object>
</child>
<child>
<object class="AdwStatusPage">
<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="icon-name">mouse-icon</property>
<property name="child">
<object class="AdwClamp">
<property name="maximum-size">600</property>
<property name="tightening-threshold">0</property>
<property name="child">
<object class="GtkBox">
<property name="orientation">vertical</property>
<property name="spacing">12</property>
<property name="content">
<object class="AdwToastOverlay" id="toast_overlay">
<child>
<object class="AdwPreferencesGroup">
<property name="title" translatable="yes">General</property>
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">enable</property>
<child type="suffix">
<object class="GtkSwitch">
<property name="valign">center</property>
<property name="tooltip-text" translatable="yes">enable</property>
</object>
</child>
</object>
</child>
<child>
<object class="AdwActionRow">
<property name="title">port</property>
<object class="AdwStatusPage">
<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="icon-name">mouse-icon</property>
<property name="child">
<object class="AdwClamp">
<property name="maximum-size">600</property>
<property name="tightening-threshold">0</property>
<property name="child">
<object class="GtkBox">
<property name="orientation">vertical</property>
<property name="spacing">12</property>
<child>
<object class="GtkEntry">
<!-- <property name="title" translatable="yes">port</property> -->
<property name="placeholder-text">4242</property>
<property name="width-chars">5</property>
<property name="xalign">0.5</property>
<property name="valign">center</property>
<!-- <property name="show-apply-button">True</property> -->
<property name="input-purpose">GTK_INPUT_PURPOSE_DIGITS</property>
<object class="AdwPreferencesGroup">
<property name="title" translatable="yes">General</property>
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">enable</property>
<child type="suffix">
<object class="GtkSwitch">
<property name="valign">center</property>
<property name="tooltip-text" translatable="yes">enable</property>
</object>
</child>
</object>
</child>
<child>
<object class="AdwActionRow">
<property name="title">port</property>
<child>
<object class="GtkEntry" id="port_entry">
<signal name="activate" handler="handle_port_edit_apply" swapped="true"/>
<signal name="changed" 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="placeholder-text">4242</property>
<property name="width-chars">5</property>
<property name="xalign">0.5</property>
<property name="valign">center</property>
<!-- <property name="show-apply-button">True</property> -->
<property name="input-purpose">GTK_INPUT_PURPOSE_DIGITS</property>
</object>
</child>
<child>
<object class="GtkButton" id="port_edit_apply">
<signal name="clicked" handler="handle_port_edit_apply" swapped="true"/>
<property name="icon-name">object-select-symbolic</property>
<property name="valign">center</property>
<property name="visible">false</property>
<property name="name">port-edit-apply</property>
</object>
</child>
<child>
<object class="GtkButton" id="port_edit_cancel">
<signal name="clicked" handler="handle_port_edit_cancel" swapped="true"/>
<property name="icon-name">process-stop-symbolic</property>
<property name="valign">center</property>
<property name="visible">false</property>
<property name="name">port-edit-cancel</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="AdwPreferencesGroup">
<property name="title" translatable="yes">Connections</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>
<child>
<object class="AdwPreferencesGroup">
<property name="title" translatable="yes">Connections</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>
</object>
</child>
<style>
<class name="boxed-list" />
</style>
</object>
</child>
</object>
</child>
<style>
<class name="boxed-list" />
</style>
<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>
</child>
</property>
</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" swapped="true"/> -->
<style>
<class name="pill"/>
</style>
</object>
</child>
</object>
</child>
</object>
</property>
</object>
</property>
</object>
</property>
</object>
</child>
</property>
</template>
</interface>

View File

@@ -453,13 +453,38 @@ impl Server {
match event {
FrontendEvent::AddClient(hostname, port, pos) => { self.add_client(hostname, HashSet::new(), port, pos).await; },
FrontendEvent::ActivateClient(client, active) => self.activate_client(client, active).await,
FrontendEvent::ChangePort(port) => {
let current_port = self.socket.local_addr().unwrap().port();
if current_port == port {
if let Err(e) = self.frontend.notify_all(FrontendNotify::NotifyPortChange(port, None)).await {
log::warn!("error notifying frontend: {e}");
}
return false;
}
let listen_addr = SocketAddr::new("0.0.0.0".parse().unwrap(), port);
match UdpSocket::bind(listen_addr).await {
Ok(socket) => {
self.socket = socket;
if let Err(e) = self.frontend.notify_all(FrontendNotify::NotifyPortChange(port, None)).await {
log::warn!("error notifying frontend: {e}");
}
},
Err(e) => {
log::warn!("could not change port: {e}");
let port = self.socket.local_addr().unwrap().port();
if let Err(e) = self.frontend.notify_all(FrontendNotify::NotifyPortChange(port, Some(format!("could not change port: {e}")))).await {
log::error!("error notifying frontend: {e}");
}
}
}
},
FrontendEvent::DelClient(client) => { self.remove_client(client).await; },
FrontendEvent::UpdateClient(client, hostname, port, pos) => self.update_client(client, hostname, port, pos).await,
FrontendEvent::Enumerate() => self.enumerate().await,
FrontendEvent::Shutdown() => {
log::info!("terminating gracefully...");
return true;
},
FrontendEvent::UpdateClient(client, hostname, port, pos) => self.update_client(client, hostname, port, pos).await,
}
false
}

View File

@@ -33,14 +33,16 @@ pub enum FrontendEvent {
AddClient(Option<String>, u16, Position),
/// activate/deactivate client
ActivateClient(ClientHandle, bool),
/// update a client (hostname, port, position)
UpdateClient(ClientHandle, Option<String>, u16, Position),
/// change the listen port (recreate udp listener)
ChangePort(u16),
/// remove a client
DelClient(ClientHandle),
/// request an enumertaion of all clients
Enumerate(),
/// service shutdown
Shutdown(),
/// update a client (hostname, port, position)
UpdateClient(ClientHandle, Option<String>, u16, Position),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -48,6 +50,8 @@ pub enum FrontendNotify {
NotifyClientCreate(ClientHandle, Option<String>, u16, Position),
NotifyClientUpdate(ClientHandle, Option<String>, u16, Position),
NotifyClientDelete(ClientHandle),
/// new port, reason of failure (if failed)
NotifyPortChange(u16, Option<String>),
Enumerate(Vec<(Client, bool)>),
NotifyError(String),
}

View File

@@ -115,6 +115,12 @@ pub fn start() -> Result<(JoinHandle<()>, JoinHandle<()>)> {
.join(", ")
);
}
},
FrontendNotify::NotifyPortChange(port, msg) => {
match msg {
Some(msg) => log::info!("could not change port: {msg}"),
None => log::info!("port changed: {port}"),
}
}
}
prompt();
@@ -142,6 +148,7 @@ fn parse_cmd(s: String, len: usize) -> Option<Vec<FrontendEvent>> {
log::info!("activate <client> activate a client");
log::info!("deactivate <client> deactivate a client");
log::info!("exit exit lan-mouse");
log::info!("setport <port> change port");
None
}
"exit" => return Some(vec![FrontendEvent::Shutdown()]),
@@ -150,13 +157,14 @@ fn parse_cmd(s: String, len: usize) -> Option<Vec<FrontendEvent>> {
"disconnect" => Some(parse_disconnect(l)),
"activate" => Some(parse_activate(l)),
"deactivate" => Some(parse_deactivate(l)),
"setport" => Some(parse_port(l)),
_ => {
log::error!("unknown command: {s}");
None
}
};
match res {
Some(Ok(e)) => Some(vec![e, FrontendEvent::Enumerate()]),
Some(Ok(e)) => Some(e),
Some(Err(e)) => {
log::warn!("{e}");
None
@@ -165,7 +173,7 @@ fn parse_cmd(s: String, len: usize) -> Option<Vec<FrontendEvent>> {
}
}
fn parse_connect(mut l: SplitWhitespace) -> Result<FrontendEvent> {
fn parse_connect(mut l: SplitWhitespace) -> Result<Vec<FrontendEvent>> {
let usage = "usage: connect <host> left|right|top|bottom [port]";
let host = l.next().context(usage)?.to_owned();
let pos = match l.next().context(usage)? {
@@ -179,19 +187,25 @@ fn parse_connect(mut l: SplitWhitespace) -> Result<FrontendEvent> {
} else {
DEFAULT_PORT
};
Ok(FrontendEvent::AddClient(Some(host), port, pos))
Ok(vec![FrontendEvent::AddClient(Some(host), port, pos), FrontendEvent::Enumerate()])
}
fn parse_disconnect(mut l: SplitWhitespace) -> Result<FrontendEvent> {
fn parse_disconnect(mut l: SplitWhitespace) -> Result<Vec<FrontendEvent>> {
let client = l.next().context("usage: disconnect <client_id>")?.parse()?;
Ok(FrontendEvent::DelClient(client))
Ok(vec![FrontendEvent::DelClient(client), FrontendEvent::Enumerate()])
}
fn parse_activate(mut l: SplitWhitespace) -> Result<FrontendEvent> {
fn parse_activate(mut l: SplitWhitespace) -> Result<Vec<FrontendEvent>> {
let client = l.next().context("usage: activate <client_id>")?.parse()?;
Ok(FrontendEvent::ActivateClient(client, true))
Ok(vec![FrontendEvent::ActivateClient(client, true), FrontendEvent::Enumerate()])
}
fn parse_deactivate(mut l: SplitWhitespace) -> Result<FrontendEvent> {
fn parse_deactivate(mut l: SplitWhitespace) -> Result<Vec<FrontendEvent>> {
let client = l.next().context("usage: deactivate <client_id>")?.parse()?;
Ok(FrontendEvent::ActivateClient(client, false))
Ok(vec![FrontendEvent::ActivateClient(client, false), FrontendEvent::Enumerate()])
}
fn parse_port(mut l: SplitWhitespace) -> Result<Vec<FrontendEvent>> {
let port = l.next().context("usage: setport <port>")?.parse()?;
Ok(vec![FrontendEvent::ChangePort(port)])
}

View File

@@ -145,6 +145,13 @@ fn build_ui(app: &Application) {
);
}
},
FrontendNotify::NotifyPortChange(port, msg) => {
match msg {
None => window.show_toast(format!("port changed: {port}").as_str()),
Some(msg) => window.show_toast(msg.as_str()),
}
window.imp().set_port(port);
}
}
glib::ControlFlow::Continue
}

View File

@@ -100,9 +100,19 @@ impl Window {
pub fn request_client_create(&self) {
let event = FrontendEvent::AddClient(None, DEFAULT_PORT, Position::default());
self.imp().set_port(DEFAULT_PORT);
self.request(event);
}
pub fn request_port_change(&self) {
let port = self.imp().port_entry.get().text().to_string();
if let Ok(port) = u16::from_str_radix(port.as_str(), 10) {
self.request(FrontendEvent::ChangePort(port));
} else {
self.request(FrontendEvent::ChangePort(DEFAULT_PORT));
}
}
pub fn request_client_update(&self, client: &ClientObject) {
let data = client.get_data();
let position = match data.position.as_str() {
@@ -150,11 +160,9 @@ impl Window {
};
}
fn setup_callbacks(&self) {
self.imp()
.add_client_button
.connect_clicked(clone!(@weak self as window => move |_| {
window.request_client_create();
}));
pub fn show_toast(&self, msg: &str) {
let toast = adw::Toast::new(msg);
let toast_overlay = &self.imp().toast_overlay;
toast_overlay.add_toast(toast);
}
}

View File

@@ -1,22 +1,32 @@
use std::{cell::{Cell, RefCell}, os::unix::net::UnixStream};
use glib::subclass::InitializingObject;
use adw::{prelude::*, ActionRow};
use adw::{ActionRow, ToastOverlay, prelude::{WidgetExt, EditableExt}};
use adw::subclass::prelude::*;
use gtk::{glib, Button, CompositeTemplate, ListBox, gio};
use gtk::{glib, Button, CompositeTemplate, ListBox, gio, Entry};
use crate::config::DEFAULT_PORT;
#[derive(CompositeTemplate, Default)]
#[template(resource = "/de/feschber/LanMouse/window.ui")]
pub struct Window {
pub number: Cell<i32>,
#[template_child]
pub port_edit_apply: TemplateChild<Button>,
#[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>,
#[template_child]
pub port_entry: TemplateChild<Entry>,
#[template_child]
pub toast_overlay: TemplateChild<ToastOverlay>,
pub clients: RefCell<Option<gio::ListStore>>,
pub stream: RefCell<Option<UnixStream>>,
pub port: Cell<u16>,
}
#[glib::object_subclass]
@@ -24,7 +34,7 @@ impl ObjectSubclass for Window {
// `NAME` needs to match `class` attribute of template
const NAME: &'static str = "LanMouseWindow";
type Type = super::Window;
type ParentType = gtk::ApplicationWindow;
type ParentType = adw::ApplicationWindow;
fn class_init(klass: &mut Self::Class) {
klass.bind_template();
@@ -39,10 +49,38 @@ impl ObjectSubclass for Window {
#[gtk::template_callbacks]
impl Window {
#[template_callback]
fn handle_button_clicked(&self, button: &Button) {
let number_increased = self.number.get() + 1;
self.number.set(number_increased);
button.set_label(&number_increased.to_string())
fn handle_add_client_pressed(&self, _button: &Button) {
self.obj().request_client_create();
}
#[template_callback]
fn handle_port_changed(&self, _entry: &Entry) {
self.port_edit_apply.set_visible(true);
self.port_edit_cancel.set_visible(true);
}
#[template_callback]
fn handle_port_edit_apply(&self) {
self.obj().request_port_change();
}
#[template_callback]
fn handle_port_edit_cancel(&self) {
log::debug!("cancel port edit");
self.port_entry.set_text(self.port.get().to_string().as_str());
self.port_edit_apply.set_visible(false);
self.port_edit_cancel.set_visible(false);
}
pub fn set_port(&self, port: u16) {
self.port.set(port);
if port == DEFAULT_PORT {
self.port_entry.set_text("");
} else {
self.port_entry.set_text(format!("{port}").as_str());
}
self.port_edit_apply.set_visible(false);
self.port_edit_cancel.set_visible(false);
}
}
@@ -50,13 +88,14 @@ impl Window {
impl ObjectImpl for Window {
fn constructed(&self) {
self.parent_constructed();
self.set_port(DEFAULT_PORT);
let obj = self.obj();
obj.setup_icon();
obj.setup_clients();
obj.setup_callbacks();
}
}
impl WidgetImpl for Window {}
impl WindowImpl for Window {}
impl ApplicationWindowImpl for Window {}
impl AdwApplicationWindowImpl for Window {}