Activate on startup (#70)

Frontends are now properly synced among each other and on startup the correct state is reflected.

Closes #75 
Closes #68
This commit is contained in:
Ferdinand Schober
2024-01-16 12:59:39 +01:00
committed by GitHub
parent 2e52660714
commit d90eb0cd0f
14 changed files with 294 additions and 224 deletions

View File

@@ -83,17 +83,26 @@ pub fn run() -> Result<()> {
Err(e) => break log::error!("{e}"),
};
match notify {
FrontendNotify::NotifyClientCreate(client, host, port, pos) => {
log::info!(
"new client ({client}): {}:{port} - {pos}",
host.as_deref().unwrap_or("")
);
FrontendNotify::NotifyClientActivate(handle, active) => {
if active {
log::info!("client {handle} activated");
} else {
log::info!("client {handle} deactivated");
}
}
FrontendNotify::NotifyClientUpdate(client, host, port, pos) => {
log::info!(
"client ({client}) updated: {}:{port} - {pos}",
host.as_deref().unwrap_or("")
);
FrontendNotify::NotifyClientCreate(client) => {
let handle = client.handle;
let port = client.port;
let pos = client.pos;
let hostname = client.hostname.as_deref().unwrap_or("");
log::info!("new client ({handle}): {hostname}:{port} - {pos}");
}
FrontendNotify::NotifyClientUpdate(client) => {
let handle = client.handle;
let port = client.port;
let pos = client.pos;
let hostname = client.hostname.as_deref().unwrap_or("");
log::info!("client ({handle}) updated: {hostname}:{port} - {pos}");
}
FrontendNotify::NotifyClientDelete(client) => {
log::info!("client ({client}) deleted.");

View File

@@ -8,16 +8,12 @@ use std::{
process, str,
};
use crate::{config::DEFAULT_PORT, frontend::gtk::window::Window};
use crate::frontend::gtk::window::Window;
use adw::Application;
use gtk::{
gdk::Display,
gio::{SimpleAction, SimpleActionGroup},
glib::clone,
prelude::*,
subclass::prelude::ObjectSubclassIsExt,
CssProvider, IconTheme,
gdk::Display, glib::clone, prelude::*, subclass::prelude::ObjectSubclassIsExt, CssProvider,
IconTheme,
};
use gtk::{gio, glib, prelude::ApplicationExt};
@@ -68,8 +64,8 @@ fn load_css() {
}
fn load_icons() {
let icon_theme =
IconTheme::for_display(&Display::default().expect("Could not connect to a display."));
let display = &Display::default().expect("Could not connect to a display.");
let icon_theme = IconTheme::for_display(display);
icon_theme.add_resource_path("/de/feschber/LanMouse/icons");
}
@@ -130,15 +126,17 @@ fn build_ui(app: &Application) {
loop {
let notify = receiver.recv().await.unwrap();
match notify {
FrontendNotify::NotifyClientCreate(client, hostname, port, position) => {
window.new_client(client, hostname, port, position, false);
FrontendNotify::NotifyClientActivate(handle, active) => {
window.activate_client(handle, active);
}
FrontendNotify::NotifyClientCreate(client) => {
window.new_client(client, false);
},
FrontendNotify::NotifyClientUpdate(client, hostname, port, position) => {
log::info!("client updated: {client}, {}:{port}, {position}", hostname.unwrap_or("".to_string()));
FrontendNotify::NotifyClientUpdate(client) => {
window.update_client(client);
}
FrontendNotify::NotifyError(e) => {
// TODO
log::error!("{e}");
window.show_toast(e.as_str());
},
FrontendNotify::NotifyClientDelete(client) => {
window.delete_client(client);
@@ -146,19 +144,11 @@ fn build_ui(app: &Application) {
FrontendNotify::Enumerate(clients) => {
for (client, active) in clients {
if window.client_idx(client.handle).is_some() {
continue
window.activate_client(client.handle, active);
window.update_client(client);
} else {
window.new_client(client, active);
}
window.new_client(
client.handle,
client.hostname,
client.addrs
.iter()
.next()
.map(|s| s.port())
.unwrap_or(DEFAULT_PORT),
client.pos,
active,
);
}
},
FrontendNotify::NotifyPortChange(port, msg) => {
@@ -172,37 +162,5 @@ fn build_ui(app: &Application) {
}
}));
let action_request_client_update =
SimpleAction::new("request-client-update", Some(&u32::static_variant_type()));
// remove client
let action_client_delete =
SimpleAction::new("request-client-delete", Some(&u32::static_variant_type()));
// update client state
action_request_client_update.connect_activate(clone!(@weak window => move |_action, param| {
log::debug!("request-client-update");
let index = param.unwrap()
.get::<u32>()
.unwrap();
let Some(client) = window.clients().item(index) else {
return;
};
let client = client.downcast_ref::<ClientObject>().unwrap();
window.request_client_update(client);
}));
action_client_delete.connect_activate(clone!(@weak window => move |_action, param| {
log::debug!("delete-client");
let idx = param.unwrap()
.get::<u32>()
.unwrap();
window.request_client_delete(idx);
}));
let actions = SimpleActionGroup::new();
window.insert_action_group("win", Some(&actions));
actions.add_action(&action_request_client_update);
actions.add_action(&action_client_delete);
window.present();
}

View File

@@ -3,26 +3,20 @@ mod imp;
use adw::subclass::prelude::*;
use gtk::glib::{self, Object};
use crate::client::ClientHandle;
use crate::client::{Client, ClientHandle};
glib::wrapper! {
pub struct ClientObject(ObjectSubclass<imp::ClientObject>);
}
impl ClientObject {
pub fn new(
handle: ClientHandle,
hostname: Option<String>,
port: u32,
position: String,
active: bool,
) -> Self {
pub fn new(client: Client, active: bool) -> Self {
Object::builder()
.property("handle", handle)
.property("hostname", hostname)
.property("port", port)
.property("handle", client.handle)
.property("hostname", client.hostname)
.property("port", client.port as u32)
.property("position", client.pos.to_string())
.property("active", active)
.property("position", position)
.build()
}

View File

@@ -28,6 +28,12 @@ impl ClientRow {
.sync_create()
.build();
let switch_position_binding = client_object
.bind_property("active", &self.imp().enable_switch.get(), "active")
.bidirectional()
.sync_create()
.build();
let hostname_binding = client_object
.bind_property("hostname", &self.imp().hostname.get(), "text")
.transform_to(|_, v: Option<String>| {
@@ -104,6 +110,7 @@ impl ClientRow {
.build();
bindings.push(active_binding);
bindings.push(switch_position_binding);
bindings.push(hostname_binding);
bindings.push(title_binding);
bindings.push(port_binding);

View File

@@ -4,6 +4,8 @@ use adw::subclass::prelude::*;
use adw::{prelude::*, ActionRow, ComboRow};
use glib::{subclass::InitializingObject, Binding};
use gtk::glib::clone;
use gtk::glib::once_cell::sync::Lazy;
use gtk::glib::subclass::Signal;
use gtk::{glib, Button, CompositeTemplate, Switch};
#[derive(CompositeTemplate, Default)]
@@ -28,6 +30,8 @@ pub struct ClientRow {
impl ObjectSubclass for ClientRow {
// `NAME` needs to match `class` attribute of template
const NAME: &'static str = "ClientRow";
const ABSTRACT: bool = false;
type Type = super::ClientRow;
type ParentType = adw::ExpanderRow;
@@ -49,28 +53,33 @@ impl ObjectImpl for ClientRow {
row.handle_client_delete(button);
}));
}
fn signals() -> &'static [glib::subclass::Signal] {
static SIGNALS: Lazy<Vec<Signal>> = Lazy::new(|| {
vec![
Signal::builder("request-update")
.param_types([bool::static_type()])
.build(),
Signal::builder("request-delete").build(),
]
});
SIGNALS.as_ref()
}
}
#[gtk::template_callbacks]
impl ClientRow {
#[template_callback]
fn handle_client_set_state(&self, state: bool, switch: &Switch) -> bool {
let idx = self.obj().index() as u32;
switch
.activate_action("win.request-client-update", Some(&idx.to_variant()))
.unwrap();
switch.set_state(state);
fn handle_client_set_state(&self, state: bool, _switch: &Switch) -> bool {
log::debug!("state change -> requesting update");
self.obj().emit_by_name::<()>("request-update", &[&state]);
true // dont run default handler
}
#[template_callback]
fn handle_client_delete(&self, button: &Button) {
log::debug!("delete button pressed");
let idx = self.obj().index() as u32;
button
.activate_action("win.request-client-delete", Some(&idx.to_variant()))
.unwrap();
fn handle_client_delete(&self, _button: &Button) {
log::debug!("delete button pressed -> requesting delete");
self.obj().emit_by_name::<()>("request-delete", &[]);
}
}

View File

@@ -5,10 +5,14 @@ use std::io::Write;
use adw::prelude::*;
use adw::subclass::prelude::*;
use glib::{clone, Object};
use gtk::{gio, glib, NoSelection};
use gtk::{
gio,
glib::{self, closure_local},
NoSelection,
};
use crate::{
client::{ClientHandle, Position},
client::{Client, ClientHandle, Position},
config::DEFAULT_PORT,
frontend::{gtk::client_object::ClientObject, FrontendEvent},
};
@@ -45,6 +49,18 @@ impl Window {
clone!(@weak self as window => @default-panic, move |obj| {
let client_object = obj.downcast_ref().expect("Expected object of type `ClientObject`.");
let row = window.create_client_row(client_object);
row.connect_closure("request-update", false, closure_local!(@strong window => move |row: ClientRow, active: bool| {
let index = row.index() as u32;
let Some(client) = window.clients().item(index) else {
return;
};
let client = client.downcast_ref::<ClientObject>().unwrap();
window.request_client_update(client, active);
}));
row.connect_closure("request-delete", false, closure_local!(@strong window => move |row: ClientRow| {
let index = row.index() as u32;
window.request_client_delete(index);
}));
row.upcast()
})
);
@@ -71,15 +87,8 @@ impl Window {
row
}
pub fn new_client(
&self,
handle: ClientHandle,
hostname: Option<String>,
port: u16,
position: Position,
active: bool,
) {
let client = ClientObject::new(handle, hostname, port as u32, position.to_string(), active);
pub fn new_client(&self, client: Client, active: bool) {
let client = ClientObject::new(client, active);
self.clients().append(&client);
self.set_placeholder_visible(false);
}
@@ -106,6 +115,42 @@ impl Window {
}
}
pub fn update_client(&self, client: Client) {
let Some(idx) = self.client_idx(client.handle) else {
log::warn!("could not find client with handle {}", client.handle);
return;
};
let client_object = self.clients().item(idx as u32).unwrap();
let client_object: &ClientObject = client_object.downcast_ref().unwrap();
let data = client_object.get_data();
/* only change if it actually has changed, otherwise
* the update signal is triggered */
if data.hostname != client.hostname {
client_object.set_hostname(client.hostname.unwrap_or("".into()));
}
if data.port != client.port as u32 {
client_object.set_port(client.port as u32);
}
if data.position != client.pos.to_string() {
client_object.set_position(client.pos.to_string());
}
}
pub fn activate_client(&self, handle: ClientHandle, active: bool) {
let Some(idx) = self.client_idx(handle) else {
log::warn!("could not find client with handle {handle}");
return;
};
let client_object = self.clients().item(idx as u32).unwrap();
let client_object: &ClientObject = client_object.downcast_ref().unwrap();
let data = client_object.get_data();
if data.active != active {
client_object.set_active(active);
log::debug!("set active to {active}");
}
}
pub fn request_client_create(&self) {
let event = FrontendEvent::AddClient(None, DEFAULT_PORT, Position::default());
self.imp().set_port(DEFAULT_PORT);
@@ -121,13 +166,10 @@ impl Window {
}
}
pub fn request_client_update(&self, client: &ClientObject) {
pub fn request_client_update(&self, client: &ClientObject, active: bool) {
let data = client.get_data();
let position = match data.position.as_str() {
"left" => Position::Left,
"right" => Position::Right,
"top" => Position::Top,
"bottom" => Position::Bottom,
let position = match Position::try_from(data.position.as_str()) {
Ok(pos) => pos,
_ => {
log::error!("invalid position: {}", data.position);
return;
@@ -135,10 +177,13 @@ impl Window {
};
let hostname = data.hostname;
let port = data.port as u16;
let event = FrontendEvent::UpdateClient(client.handle(), hostname, port, position);
log::debug!("requesting update: {event:?}");
self.request(event);
let event = FrontendEvent::ActivateClient(client.handle(), !client.active());
let event = FrontendEvent::ActivateClient(client.handle(), active);
log::debug!("requesting activate: {event:?}");
self.request(event);
}

View File

@@ -6,10 +6,7 @@ use std::net::TcpStream;
use std::os::unix::net::UnixStream;
use adw::subclass::prelude::*;
use adw::{
prelude::{EditableExt, WidgetExt},
ActionRow, ToastOverlay,
};
use adw::{prelude::*, ActionRow, ToastOverlay};
use glib::subclass::InitializingObject;
use gtk::{gio, glib, Button, CompositeTemplate, Entry, ListBox};
@@ -42,6 +39,8 @@ pub struct Window {
impl ObjectSubclass for Window {
// `NAME` needs to match `class` attribute of template
const NAME: &'static str = "LanMouseWindow";
const ABSTRACT: bool = false;
type Type = super::Window;
type ParentType = adw::ApplicationWindow;