mirror of
https://github.com/feschber/lan-mouse.git
synced 2026-03-07 11:59:59 +03:00
impl fingerprint ui logic
This commit is contained in:
@@ -274,6 +274,12 @@ impl Cli {
|
||||
FrontendEvent::EmulationStatus(s) => {
|
||||
eprintln!("emulation status: {s:?}")
|
||||
}
|
||||
FrontendEvent::AuthorizedUpdated(keys) => {
|
||||
eprintln!("authorized keys changed:");
|
||||
for key in keys {
|
||||
eprintln!("{key}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -47,7 +47,8 @@
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="halign">center</property>
|
||||
<child>
|
||||
<object class="GtkButton">
|
||||
<object class="GtkButton" id="confirm_button">
|
||||
<signal name="clicked" handler="handle_confirm" swapped="true"/>
|
||||
<property name="label" translatable="yes">Confirm</property>
|
||||
<property name="can-shrink">True</property>
|
||||
<style>
|
||||
|
||||
17
lan-mouse-gtk/resources/key_row.ui
Normal file
17
lan-mouse-gtk/resources/key_row.ui
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interface>
|
||||
<template class="KeyRow" parent="AdwActionRow">
|
||||
<property name="title">hostname</property>
|
||||
<child type="prefix">
|
||||
<object class="GtkButton" id="delete_button">
|
||||
<property name="valign">center</property>
|
||||
<property name="halign">end</property>
|
||||
<property name="tooltip-text" translatable="yes">enable</property>
|
||||
<property name="icon-name">edit-delete-symbolic</property>
|
||||
<style>
|
||||
<class name="flat"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
||||
@@ -4,6 +4,7 @@
|
||||
<file compressed="true" preprocess="xml-stripblanks">window.ui</file>
|
||||
<file compressed="true" preprocess="xml-stripblanks">fingerprint_window.ui</file>
|
||||
<file compressed="true" preprocess="xml-stripblanks">client_row.ui</file>
|
||||
<file compressed="true" preprocess="xml-stripblanks">key_row.ui</file>
|
||||
</gresource>
|
||||
<gresource prefix="/de/feschber/LanMouse/icons">
|
||||
<file compressed="true" preprocess="xml-stripblanks">de.feschber.LanMouse.svg</file>
|
||||
|
||||
@@ -240,7 +240,7 @@
|
||||
<signal name="clicked" handler="handle_add_cert_fingerprint" swapped="true"/>
|
||||
<property name="child">
|
||||
<object class="AdwButtonContent">
|
||||
<property name="icon-name">list-add-symbolic</property>
|
||||
<property name="icon-name">auth-fingerprint-symbolic</property>
|
||||
<property name="label" translatable="yes">Add</property>
|
||||
</object>
|
||||
</property>
|
||||
@@ -250,10 +250,10 @@
|
||||
</object>
|
||||
</property>
|
||||
<child>
|
||||
<object class="GtkListBox" id="fingerprint_list">
|
||||
<object class="GtkListBox" id="authorized_list">
|
||||
<property name="selection-mode">none</property>
|
||||
<child type="placeholder">
|
||||
<object class="AdwActionRow">
|
||||
<object class="AdwActionRow" id="authorized_placeholder">
|
||||
<property name="title">no fingerprints!</property>
|
||||
<property name="subtitle">add a public key fingerprint via the + button</property>
|
||||
</object>
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
mod imp;
|
||||
|
||||
use adw::prelude::*;
|
||||
use adw::subclass::prelude::*;
|
||||
use glib::{clone, Object};
|
||||
use gtk::{
|
||||
gio,
|
||||
glib::{self, closure_local},
|
||||
ListBox, NoSelection,
|
||||
};
|
||||
use glib::Object;
|
||||
use gtk::{gio, glib};
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct FingerprintWindow(ObjectSubclass<imp::FingerprintWindow>)
|
||||
|
||||
@@ -1,12 +1,20 @@
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use adw::prelude::*;
|
||||
use adw::subclass::prelude::*;
|
||||
use glib::subclass::InitializingObject;
|
||||
use gtk::{glib, template_callbacks, CompositeTemplate, Entry};
|
||||
use gtk::{
|
||||
glib::{self, subclass::Signal},
|
||||
template_callbacks, Button, CompositeTemplate, Text,
|
||||
};
|
||||
|
||||
#[derive(CompositeTemplate, Default)]
|
||||
#[template(resource = "/de/feschber/LanMouse/fingerprint_window.ui")]
|
||||
pub struct FingerprintWindow {
|
||||
// #[template_child]
|
||||
// pub fingerprint_entry: TemplateChild<Entry>,
|
||||
#[template_child]
|
||||
pub text: TemplateChild<Text>,
|
||||
#[template_child]
|
||||
pub confirm_button: TemplateChild<Button>,
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
@@ -29,13 +37,21 @@ impl ObjectSubclass for FingerprintWindow {
|
||||
|
||||
#[template_callbacks]
|
||||
impl FingerprintWindow {
|
||||
// #[template_callback]
|
||||
// fn handle_confirm() {}
|
||||
#[template_callback]
|
||||
fn handle_confirm(&self, _button: Button) {
|
||||
let fp = self.text.text().to_string();
|
||||
self.obj().emit_by_name("confirm-clicked", &[&fp])
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectImpl for FingerprintWindow {
|
||||
fn constructed(&self) {
|
||||
self.parent_constructed();
|
||||
fn signals() -> &'static [Signal] {
|
||||
static SIGNALS: OnceLock<Vec<Signal>> = OnceLock::new();
|
||||
SIGNALS.get_or_init(|| {
|
||||
vec![Signal::builder("confirm-clicked")
|
||||
.param_types([String::static_type()])
|
||||
.build()]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
18
lan-mouse-gtk/src/key_object.rs
Normal file
18
lan-mouse-gtk/src/key_object.rs
Normal file
@@ -0,0 +1,18 @@
|
||||
mod imp;
|
||||
|
||||
use adw::subclass::prelude::*;
|
||||
use gtk::glib::{self, Object};
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct KeyObject(ObjectSubclass<imp::KeyObject>);
|
||||
}
|
||||
|
||||
impl KeyObject {
|
||||
pub fn new(fp: String) -> Self {
|
||||
Object::builder().property("fingerprint", fp).build()
|
||||
}
|
||||
|
||||
pub fn get_fingerprint(&self) -> String {
|
||||
self.imp().fingerprint.borrow().clone()
|
||||
}
|
||||
}
|
||||
22
lan-mouse-gtk/src/key_object/imp.rs
Normal file
22
lan-mouse-gtk/src/key_object/imp.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
use std::cell::RefCell;
|
||||
|
||||
use glib::Properties;
|
||||
use gtk::glib;
|
||||
use gtk::prelude::*;
|
||||
use gtk::subclass::prelude::*;
|
||||
|
||||
#[derive(Properties, Default)]
|
||||
#[properties(wrapper_type = super::KeyObject)]
|
||||
pub struct KeyObject {
|
||||
#[property(name = "fingerprint", get, set, type = String)]
|
||||
pub fingerprint: RefCell<String>,
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for KeyObject {
|
||||
const NAME: &'static str = "KeyObject";
|
||||
type Type = super::KeyObject;
|
||||
}
|
||||
|
||||
#[glib::derived_properties]
|
||||
impl ObjectImpl for KeyObject {}
|
||||
36
lan-mouse-gtk/src/key_row.rs
Normal file
36
lan-mouse-gtk/src/key_row.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
mod imp;
|
||||
|
||||
use adw::prelude::*;
|
||||
use adw::subclass::prelude::*;
|
||||
use gtk::glib::{self, Object};
|
||||
|
||||
use super::KeyObject;
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct KeyRow(ObjectSubclass<imp::KeyRow>)
|
||||
@extends gtk::ListBoxRow, gtk::Widget, adw::PreferencesRow, adw::ExpanderRow,
|
||||
@implements gtk::Accessible, gtk::Actionable, gtk::Buildable, gtk::ConstraintTarget;
|
||||
}
|
||||
|
||||
impl KeyRow {
|
||||
pub fn new() -> Self {
|
||||
Object::builder().build()
|
||||
}
|
||||
|
||||
pub fn bind(&self, key_object: &KeyObject) {
|
||||
let mut bindings = self.imp().bindings.borrow_mut();
|
||||
|
||||
let title_binding = key_object
|
||||
.bind_property("fingerprint", self, "title")
|
||||
.sync_create()
|
||||
.build();
|
||||
|
||||
bindings.push(title_binding);
|
||||
}
|
||||
|
||||
pub fn unbind(&self) {
|
||||
for binding in self.imp().bindings.borrow_mut().drain(..) {
|
||||
binding.unbind();
|
||||
}
|
||||
}
|
||||
}
|
||||
68
lan-mouse-gtk/src/key_row/imp.rs
Normal file
68
lan-mouse-gtk/src/key_row/imp.rs
Normal file
@@ -0,0 +1,68 @@
|
||||
use std::cell::RefCell;
|
||||
|
||||
use adw::subclass::prelude::*;
|
||||
use adw::{prelude::*, ActionRow};
|
||||
use glib::{subclass::InitializingObject, Binding};
|
||||
use gtk::glib::clone;
|
||||
use gtk::glib::subclass::Signal;
|
||||
use gtk::{glib, Button, CompositeTemplate};
|
||||
use std::sync::OnceLock;
|
||||
|
||||
#[derive(CompositeTemplate, Default)]
|
||||
#[template(resource = "/de/feschber/LanMouse/key_row.ui")]
|
||||
pub struct KeyRow {
|
||||
#[template_child]
|
||||
pub delete_button: TemplateChild<gtk::Button>,
|
||||
pub bindings: RefCell<Vec<Binding>>,
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for KeyRow {
|
||||
// `NAME` needs to match `class` attribute of template
|
||||
const NAME: &'static str = "KeyRow";
|
||||
const ABSTRACT: bool = false;
|
||||
|
||||
type Type = super::KeyRow;
|
||||
type ParentType = ActionRow;
|
||||
|
||||
fn class_init(klass: &mut Self::Class) {
|
||||
klass.bind_template();
|
||||
klass.bind_template_callbacks();
|
||||
}
|
||||
|
||||
fn instance_init(obj: &InitializingObject<Self>) {
|
||||
obj.init_template();
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectImpl for KeyRow {
|
||||
fn constructed(&self) {
|
||||
self.parent_constructed();
|
||||
self.delete_button.connect_clicked(clone!(
|
||||
#[weak(rename_to = row)]
|
||||
self,
|
||||
move |button| {
|
||||
row.handle_delete(button);
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
fn signals() -> &'static [glib::subclass::Signal] {
|
||||
static SIGNALS: OnceLock<Vec<Signal>> = OnceLock::new();
|
||||
SIGNALS.get_or_init(|| vec![Signal::builder("request-delete").build()])
|
||||
}
|
||||
}
|
||||
|
||||
#[gtk::template_callbacks]
|
||||
impl KeyRow {
|
||||
#[template_callback]
|
||||
fn handle_delete(&self, _button: &Button) {
|
||||
self.obj().emit_by_name::<()>("request-delete", &[]);
|
||||
}
|
||||
}
|
||||
|
||||
impl WidgetImpl for KeyRow {}
|
||||
impl BoxImpl for KeyRow {}
|
||||
impl ListBoxRowImpl for KeyRow {}
|
||||
impl PreferencesRowImpl for KeyRow {}
|
||||
impl ActionRowImpl for KeyRow {}
|
||||
@@ -1,6 +1,8 @@
|
||||
mod client_object;
|
||||
mod client_row;
|
||||
mod fingerprint_window;
|
||||
mod key_object;
|
||||
mod key_row;
|
||||
mod window;
|
||||
|
||||
use std::{env, process, str};
|
||||
@@ -16,6 +18,7 @@ use gtk::{
|
||||
use gtk::{gio, glib, prelude::ApplicationExt};
|
||||
|
||||
use self::client_object::ClientObject;
|
||||
use self::key_object::KeyObject;
|
||||
|
||||
pub fn run() -> glib::ExitCode {
|
||||
log::debug!("running gtk frontend");
|
||||
@@ -133,6 +136,9 @@ fn build_ui(app: &Application) {
|
||||
FrontendEvent::EmulationStatus(s) => {
|
||||
window.set_emulation(s.into());
|
||||
}
|
||||
FrontendEvent::AuthorizedUpdated(keys) => {
|
||||
window.set_authorized_keys(keys);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
mod imp;
|
||||
|
||||
use std::collections::HashSet;
|
||||
|
||||
use adw::prelude::*;
|
||||
use adw::subclass::prelude::*;
|
||||
use glib::{clone, Object};
|
||||
use gtk::{
|
||||
gio,
|
||||
glib::{self, closure_local},
|
||||
ListBox, NoSelection, Widget,
|
||||
ListBox, NoSelection,
|
||||
};
|
||||
|
||||
use lan_mouse_ipc::{
|
||||
@@ -14,7 +16,7 @@ use lan_mouse_ipc::{
|
||||
DEFAULT_PORT,
|
||||
};
|
||||
|
||||
use crate::fingerprint_window::FingerprintWindow;
|
||||
use crate::{fingerprint_window::FingerprintWindow, key_object::KeyObject, key_row::KeyRow};
|
||||
|
||||
use super::{client_object::ClientObject, client_row::ClientRow};
|
||||
|
||||
@@ -44,10 +46,55 @@ impl Window {
|
||||
.expect("Could not get clients")
|
||||
}
|
||||
|
||||
pub fn authorized(&self) -> gio::ListStore {
|
||||
self.imp()
|
||||
.authorized
|
||||
.borrow()
|
||||
.clone()
|
||||
.expect("Could not get authorized")
|
||||
}
|
||||
|
||||
fn client_by_idx(&self, idx: u32) -> Option<ClientObject> {
|
||||
self.clients().item(idx).map(|o| o.downcast().unwrap())
|
||||
}
|
||||
|
||||
fn authorized_by_idx(&self, idx: u32) -> Option<KeyObject> {
|
||||
self.authorized().item(idx).map(|o| o.downcast().unwrap())
|
||||
}
|
||||
|
||||
fn setup_authorized(&self) {
|
||||
let store = gio::ListStore::new::<KeyObject>();
|
||||
self.imp().authorized.replace(Some(store));
|
||||
let selection_model = NoSelection::new(Some(self.authorized()));
|
||||
self.imp().authorized_list.bind_model(
|
||||
Some(&selection_model),
|
||||
clone!(
|
||||
#[weak(rename_to = window)]
|
||||
self,
|
||||
#[upgrade_or_panic]
|
||||
move |obj| {
|
||||
let key_obj = obj.downcast_ref().expect("object of type `KeyObject`");
|
||||
let row = window.create_key_row(key_obj);
|
||||
row.connect_closure(
|
||||
"request-delete",
|
||||
false,
|
||||
closure_local!(
|
||||
#[strong]
|
||||
window,
|
||||
move |row: KeyRow| {
|
||||
if let Some(key_obj) = window.authorized_by_idx(row.index() as u32)
|
||||
{
|
||||
window.request_fingerprint_remove(key_obj.get_fingerprint());
|
||||
}
|
||||
}
|
||||
),
|
||||
);
|
||||
row.upcast()
|
||||
}
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn setup_clients(&self) {
|
||||
let model = gio::ListStore::new::<ClientObject>();
|
||||
self.imp().clients.replace(Some(model));
|
||||
@@ -116,7 +163,8 @@ impl Window {
|
||||
/// workaround for a bug in libadwaita that shows an ugly line beneath
|
||||
/// the last element if a placeholder is set.
|
||||
/// https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/6308
|
||||
pub fn set_placeholder_visible(&self, visible: bool) {
|
||||
pub fn update_placeholder_visibility(&self) {
|
||||
let visible = self.clients().n_items() == 0;
|
||||
let placeholder = self.imp().client_placeholder.get();
|
||||
self.imp().client_list.set_placeholder(match visible {
|
||||
true => Some(&placeholder),
|
||||
@@ -124,6 +172,15 @@ impl Window {
|
||||
});
|
||||
}
|
||||
|
||||
pub fn update_auth_placeholder_visibility(&self) {
|
||||
let visible = self.authorized().n_items() == 0;
|
||||
let placeholder = self.imp().authorized_placeholder.get();
|
||||
self.imp().authorized_list.set_placeholder(match visible {
|
||||
true => Some(&placeholder),
|
||||
false => None,
|
||||
});
|
||||
}
|
||||
|
||||
fn setup_icon(&self) {
|
||||
self.set_icon_name(Some("de.feschber.LanMouse"));
|
||||
}
|
||||
@@ -134,10 +191,16 @@ impl Window {
|
||||
row
|
||||
}
|
||||
|
||||
fn create_key_row(&self, key_object: &KeyObject) -> KeyRow {
|
||||
let row = KeyRow::new();
|
||||
row.bind(key_object);
|
||||
row
|
||||
}
|
||||
|
||||
pub fn new_client(&self, handle: ClientHandle, client: ClientConfig, state: ClientState) {
|
||||
let client = ClientObject::new(handle, client, state.clone());
|
||||
self.clients().append(&client);
|
||||
self.set_placeholder_visible(false);
|
||||
self.update_placeholder_visibility();
|
||||
self.update_dns_state(handle, !state.ips.is_empty());
|
||||
}
|
||||
|
||||
@@ -159,7 +222,7 @@ impl Window {
|
||||
|
||||
self.clients().remove(idx as u32);
|
||||
if self.clients().n_items() == 0 {
|
||||
self.set_placeholder_visible(true);
|
||||
self.update_placeholder_visibility();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -291,6 +354,18 @@ impl Window {
|
||||
pub fn open_fingerprint_dialog(&self) {
|
||||
let window = FingerprintWindow::new();
|
||||
window.set_transient_for(Some(self));
|
||||
window.connect_closure(
|
||||
"confirm-clicked",
|
||||
false,
|
||||
closure_local!(
|
||||
#[strong(rename_to = parent)]
|
||||
self,
|
||||
move |w: FingerprintWindow, fp: String| {
|
||||
parent.request_fingerprint_add(fp);
|
||||
w.close();
|
||||
}
|
||||
),
|
||||
);
|
||||
window.present();
|
||||
}
|
||||
|
||||
@@ -298,6 +373,10 @@ impl Window {
|
||||
self.request(FrontendRequest::FingerprintAdd(fp));
|
||||
}
|
||||
|
||||
pub fn request_fingerprint_remove(&self, fp: String) {
|
||||
self.request(FrontendRequest::FingerprintRemove(fp));
|
||||
}
|
||||
|
||||
pub fn request(&self, request: FrontendRequest) {
|
||||
let mut requester = self.imp().frontend_request_writer.borrow_mut();
|
||||
let requester = requester.as_mut().unwrap();
|
||||
@@ -331,4 +410,16 @@ impl Window {
|
||||
.capture_emulation_group
|
||||
.set_visible(!capture || !emulation);
|
||||
}
|
||||
|
||||
pub(crate) fn set_authorized_keys(&self, fingerprints: HashSet<String>) {
|
||||
let authorized = self.authorized();
|
||||
// clear list
|
||||
authorized.remove_all();
|
||||
// insert fingerprints
|
||||
for fingerprint in fingerprints {
|
||||
let key_obj = KeyObject::new(fingerprint);
|
||||
authorized.append(&key_obj);
|
||||
}
|
||||
self.update_auth_placeholder_visibility();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ use lan_mouse_ipc::{FrontendRequestWriter, DEFAULT_PORT};
|
||||
#[derive(CompositeTemplate, Default)]
|
||||
#[template(resource = "/de/feschber/LanMouse/window.ui")]
|
||||
pub struct Window {
|
||||
#[template_child]
|
||||
pub authorized_placeholder: TemplateChild<ActionRow>,
|
||||
#[template_child]
|
||||
pub port_edit_apply: TemplateChild<Button>,
|
||||
#[template_child]
|
||||
@@ -35,7 +37,10 @@ pub struct Window {
|
||||
pub input_emulation_button: TemplateChild<Button>,
|
||||
#[template_child]
|
||||
pub input_capture_button: TemplateChild<Button>,
|
||||
#[template_child]
|
||||
pub authorized_list: TemplateChild<ListBox>,
|
||||
pub clients: RefCell<Option<gio::ListStore>>,
|
||||
pub authorized: RefCell<Option<gio::ListStore>>,
|
||||
pub frontend_request_writer: RefCell<Option<FrontendRequestWriter>>,
|
||||
pub port: Cell<u16>,
|
||||
pub capture_active: Cell<bool>,
|
||||
@@ -146,6 +151,7 @@ impl ObjectImpl for Window {
|
||||
let obj = self.obj();
|
||||
obj.setup_icon();
|
||||
obj.setup_clients();
|
||||
obj.setup_authorized();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -197,6 +197,7 @@ pub enum FrontendEvent {
|
||||
CaptureStatus(Status),
|
||||
/// emulation status
|
||||
EmulationStatus(Status),
|
||||
AuthorizedUpdated(HashSet<String>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
|
||||
|
||||
@@ -270,10 +270,16 @@ impl Server {
|
||||
|
||||
fn add_authorized_key(&self, key: String) {
|
||||
self.authorized_keys.borrow_mut().insert(key);
|
||||
self.notify_frontend(FrontendEvent::AuthorizedUpdated(
|
||||
self.authorized_keys.borrow().clone(),
|
||||
));
|
||||
}
|
||||
|
||||
fn remove_authorized_key(&self, key: String) {
|
||||
self.authorized_keys.borrow_mut().remove(&key);
|
||||
self.notify_frontend(FrontendEvent::AuthorizedUpdated(
|
||||
self.authorized_keys.borrow().clone(),
|
||||
));
|
||||
}
|
||||
|
||||
fn enumerate(&self) {
|
||||
|
||||
Reference in New Issue
Block a user