impl fingerprint ui logic

This commit is contained in:
Ferdinand Schober
2024-09-27 14:21:36 +02:00
parent 0038178f0d
commit 1c7490c58d
16 changed files with 313 additions and 24 deletions

View File

@@ -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>)

View File

@@ -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()]
})
}
}

View 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()
}
}

View 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 {}

View 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();
}
}
}

View 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 {}

View File

@@ -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);
}
}
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}