diff --git a/Linux/.vscode/settings.json b/Linux/.vscode/settings.json index f21c8f7..bf73d85 100644 --- a/Linux/.vscode/settings.json +++ b/Linux/.vscode/settings.json @@ -1,4 +1,5 @@ { "nixEnvSelector.nixFile": "${workspaceFolder}/shell.nix", - "files.trimTrailingWhitespace": true + "files.trimTrailingWhitespace": true, + "rust-analyzer.imports.granularity.group": "item" } \ No newline at end of file diff --git a/Linux/Cargo.lock b/Linux/Cargo.lock index 3649d22..0dc4150 100644 --- a/Linux/Cargo.lock +++ b/Linux/Cargo.lock @@ -17,6 +17,8 @@ dependencies = [ "rust-i18n", "serde", "serde_json", + "serialport", + "tracker", ] [[package]] @@ -211,6 +213,12 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + [[package]] name = "crc32fast" version = "1.4.2" @@ -803,6 +811,16 @@ dependencies = [ "hashbrown 0.14.5", ] +[[package]] +name = "io-kit-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617ee6cf8e3f66f3b4ea67a4058564628cde41901316e19f559e14c7c72c5e7b" +dependencies = [ + "core-foundation-sys", + "mach2", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.0" @@ -868,6 +886,15 @@ version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +[[package]] +name = "mach2" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709" +dependencies = [ + "libc", +] + [[package]] name = "memchr" version = "2.7.2" @@ -909,6 +936,17 @@ dependencies = [ "getrandom", ] +[[package]] +name = "nix" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", +] + [[package]] name = "normpath" version = "1.2.0" @@ -1271,6 +1309,24 @@ dependencies = [ "yaml-rust", ] +[[package]] +name = "serialport" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de7c4f0cce25b9b3518eea99618112f9ee4549f974480c8f43d3c06f03c131a0" +dependencies = [ + "bitflags 2.5.0", + "cfg-if", + "core-foundation-sys", + "io-kit-sys", + "mach2", + "nix", + "regex", + "scopeguard", + "unescaper", + "winapi", +] + [[package]] name = "slab" version = "0.4.9" @@ -1480,6 +1536,26 @@ dependencies = [ "once_cell", ] +[[package]] +name = "tracker" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce5c98457ff700aaeefcd4a4a492096e78a2af1dd8523c66e94a3adb0fdbd415" +dependencies = [ + "tracker-macros", +] + +[[package]] +name = "tracker-macros" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc19eb2373ccf3d1999967c26c3d44534ff71ae5d8b9dacf78f4b13132229e48" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + [[package]] name = "triomphe" version = "0.1.12" @@ -1491,6 +1567,15 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "unescaper" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c878a167baa8afd137494101a688ef8c67125089ff2249284bd2b5f9bfedb815" +dependencies = [ + "thiserror", +] + [[package]] name = "unicode-ident" version = "1.0.12" diff --git a/Linux/Cargo.toml b/Linux/Cargo.toml index ae63d8c..cbe35ba 100644 --- a/Linux/Cargo.toml +++ b/Linux/Cargo.toml @@ -15,6 +15,8 @@ relm4-icons = "0.8.3" rust-i18n = "3.0.1" serde = { version = "1.0.203", features = ["derive"] } serde_json = "1.0.117" +serialport = { version = "4.4.0", default-features = false } +tracker = "0.2.2" [dependencies.min-rs] -git = "https://github.com/MvRens/min-rs.git" \ No newline at end of file +git = "https://github.com/MvRens/min-rs.git" diff --git a/Linux/locales/devices/serial_min.yaml b/Linux/locales/devices/serial_min.yaml index a1d1455..5834114 100644 --- a/Linux/locales/devices/serial_min.yaml +++ b/Linux/locales/devices/serial_min.yaml @@ -3,4 +3,14 @@ _version: 2 devices: serial_min: name: - en: Serial device using MIN protocol \ No newline at end of file + en: Serial device using MIN protocol + +serial_min: + settings: + port: + label: + en: Serial port + custom: + en: Custom... + custom_port_placeholder: + en: e.g. /dev/ttyACM0 \ No newline at end of file diff --git a/Linux/shell.nix b/Linux/shell.nix index 67ece15..d8cf99a 100644 --- a/Linux/shell.nix +++ b/Linux/shell.nix @@ -1,10 +1,11 @@ { pkgs ? import {} }: pkgs.mkShell { - nativeBuildInputs = with pkgs; [ + nativeBuildInputs = with pkgs; [ pkg-config gtk4 graphene gdk-pixbuf + libudev-zero ]; # For a reason I'm yet to find out, my VSCode terminal sets GDK_BACKEND to x11. diff --git a/Linux/src/actions/pipewire/mod.rs b/Linux/src/actions/pipewire/mod.rs index cbef414..b300aa7 100644 --- a/Linux/src/actions/pipewire/mod.rs +++ b/Linux/src/actions/pipewire/mod.rs @@ -9,6 +9,6 @@ pub fn register(registry: &mut Registry) { registry.register(ActionRegistryItem { - unique_id: UniqueId::new("pipewire.set_volume") + unique_id: UniqueId::from("pipewire.set_volume") }); } \ No newline at end of file diff --git a/Linux/src/devices/emulator/emulatorwindow.rs b/Linux/src/devices/emulator/emulatorwindow.rs index ddf3428..93c68ca 100644 --- a/Linux/src/devices/emulator/emulatorwindow.rs +++ b/Linux/src/devices/emulator/emulatorwindow.rs @@ -2,12 +2,12 @@ use gtk::prelude::*; use relm4::prelude::*; pub struct EmulatorWindow -{ +{ } #[derive(Debug)] -pub enum Msg +pub enum EmulatorWindowMessage { } @@ -16,20 +16,20 @@ pub enum Msg impl SimpleComponent for EmulatorWindow { type Init = (); - type Input = Msg; + type Input = EmulatorWindowMessage; type Output = (); view! - { + { gtk::Window - { + { set_title: Some(&t!("emulatorwindow.title")), set_default_size: (300, 500) } } - fn init(_data: Self::Init, root: Self::Root, _sender: ComponentSender, ) -> ComponentParts + fn init(_data: Self::Init, root: Self::Root, _sender: ComponentSender, ) -> ComponentParts { let model = EmulatorWindow {}; let widgets = view_output!(); @@ -39,10 +39,10 @@ impl SimpleComponent for EmulatorWindow } - fn update(&mut self, msg: Self::Input, _sender: ComponentSender) + fn update(&mut self, msg: Self::Input, _sender: ComponentSender) { - match msg + match msg { } - } + } } \ No newline at end of file diff --git a/Linux/src/devices/emulator/mod.rs b/Linux/src/devices/emulator/mod.rs index e84cb03..63d828d 100644 --- a/Linux/src/devices/emulator/mod.rs +++ b/Linux/src/devices/emulator/mod.rs @@ -1,11 +1,13 @@ use emulatorwindow::EmulatorWindow; -use relm4::{component::Connector, ComponentController}; +use relm4::component::Connector; use relm4::Component; use relm4::gtk::prelude::*; +use relm4::ComponentController; use crate::registry::Registry; use crate::util::unique_id::UniqueId; -use super::{Device, DeviceRegistryItem}; +use super::Device; +use super::DeviceRegistryItem; pub mod emulatorwindow; @@ -13,7 +15,7 @@ pub mod emulatorwindow; pub struct EmulatorWindowDevice { - window: Option> + window: Connector } @@ -21,9 +23,12 @@ impl EmulatorWindowDevice { fn new() -> Self { + let builder = EmulatorWindow::builder(); + let window = builder.launch({}); + EmulatorWindowDevice { - window: None + window } } } @@ -31,20 +36,14 @@ impl EmulatorWindowDevice impl Device for EmulatorWindowDevice { - fn activate(&mut self) +} + + +impl Drop for EmulatorWindowDevice +{ + fn drop(&mut self) { - if self.window.is_some() { return } - - let builder = EmulatorWindow::builder(); - self.window = Some(builder.launch({})); - } - - - fn deactivate(&mut self) - { - let Some(window) = self.window.take() else { return }; - - window.widget().close(); + self.window.widget().close(); } } @@ -52,7 +51,8 @@ impl Device for EmulatorWindowDevice pub fn register(registry: &mut Registry) { registry.register(DeviceRegistryItem { - unique_id: UniqueId::new("emulator"), - factory: || Box::new(EmulatorWindowDevice::new()) + unique_id: UniqueId::from("emulator"), + factory: || Box::new(EmulatorWindowDevice::new()), + settings_widget_factory: || None }); } \ No newline at end of file diff --git a/Linux/src/devices/mod.rs b/Linux/src/devices/mod.rs index a1a4736..f21a6a1 100644 --- a/Linux/src/devices/mod.rs +++ b/Linux/src/devices/mod.rs @@ -4,20 +4,20 @@ pub mod serial_min; use crate::registry::Registry; use crate::registry::RegistryItem; +use crate::ui::EmbeddedWidgetConnector; use crate::util::unique_id::UniqueId; pub struct DeviceRegistryItem { pub unique_id: UniqueId, - pub factory: fn() -> Box + pub factory: fn() -> Box, + pub settings_widget_factory: fn() -> Option> } pub trait Device { - fn activate(&mut self); - fn deactivate(&mut self); } diff --git a/Linux/src/devices/serial_min/mod.rs b/Linux/src/devices/serial_min/mod.rs index 32abf8d..7238162 100644 --- a/Linux/src/devices/serial_min/mod.rs +++ b/Linux/src/devices/serial_min/mod.rs @@ -1,6 +1,14 @@ +use relm4::Component; +use settingswidget::SerialMinSettingsInit; +use settingswidget::SerialMinSettingsWidget; + use crate::registry::Registry; use crate::util::unique_id::UniqueId; -use super::{Device, DeviceRegistryItem}; +use super::Device; +use super::DeviceRegistryItem; + + +pub mod settingswidget; pub struct SerialMinDevice @@ -11,23 +19,20 @@ pub struct SerialMinDevice impl Device for SerialMinDevice { - fn activate(&mut self) - { - //todo!() - } - - - fn deactivate(&mut self) - { - //todo!() - } } pub fn register(registry: &mut Registry) { registry.register(DeviceRegistryItem { - unique_id: UniqueId::new("serial_min"), - factory: || Box::new(SerialMinDevice {}) + unique_id: UniqueId::from("serial_min"), + factory: || Box::new(SerialMinDevice {}), + settings_widget_factory: || + { + let builder = SerialMinSettingsWidget::builder(); + Some(Box::new(builder.launch(SerialMinSettingsInit + { + }))) + } }); } \ No newline at end of file diff --git a/Linux/src/devices/serial_min/settingswidget.rs b/Linux/src/devices/serial_min/settingswidget.rs new file mode 100644 index 0000000..a08580e --- /dev/null +++ b/Linux/src/devices/serial_min/settingswidget.rs @@ -0,0 +1,183 @@ +use gtk::StringList; +use gtk::glib::clone; +use relm4::component::Connector; +use relm4::prelude::*; +use relm4::gtk::prelude::*; +use serialport::SerialPortInfo; + +use crate::ui::EmbeddedWidgetConnector; + + +#[tracker::track] +pub struct SerialMinSettingsWidget +{ + #[do_not_track] + ports: Vec, + + custom_port: String, + custom_port_visible: bool +} + + +#[derive(Debug)] +pub enum SerialMinSettingsWidgetMessage +{ + PortChanged(usize), + CustomPortChanged(String) +} + + +pub struct SerialMinSettingsInit +{ + // this needs a good design - we need to be able to modify the device instance, but + // we can't pass the reference or an Rc to the device due to the design of create_settings_widget. + // Either seperate the UI from the Device, or pass a middle man (sender/receiver style perhaps?) +} + + +pub struct SerialMinSettingsWidgets +{ + custom_port_input: gtk::Entry +} + + +impl SimpleComponent for SerialMinSettingsWidget +{ + type Init = SerialMinSettingsInit; + type Input = SerialMinSettingsWidgetMessage; + type Output = (); + type Root = gtk::Box; + type Widgets = SerialMinSettingsWidgets; + + + fn init_root() -> Self::Root + { + gtk::Box::builder() + .hexpand(true) + .orientation(gtk::Orientation::Vertical) + .spacing(8) + .build() + } + + + fn init(_data: Self::Init, root: Self::Root, sender: ComponentSender, ) -> ComponentParts + { + let port_label = gtk::Label::builder() + .label(t!("serial_min.settings.port.label")) + .halign(gtk::Align::Start) + .build(); + + root.append(&port_label); + + + let ports_list = serialport::available_ports().unwrap_or(Vec::::new()); + let ports: Vec = ports_list.iter().map(|p| p.port_name.clone()).collect(); + + let port_model_vec: Vec<&str> = ports.iter().map(|p| p.as_str()).collect(); + let port_model = StringList::new(&port_model_vec); + + port_model.append(t!("serial_min.settings.port.custom").as_ref()); + + let port_select = gtk::DropDown::builder() + .model(&port_model) + .selected(ports.len().try_into().unwrap_or(gtk::ffi::GTK_INVALID_LIST_POSITION)) + .build(); + + root.append(&port_select); + + + let port_select_cloned = port_select.clone(); + port_select.connect_selected_notify(clone!( + @strong sender => move |_| + { + let active_index = port_select_cloned.selected(); + if active_index == gtk::ffi::GTK_INVALID_LIST_POSITION { return }; + + if let Ok(active_index_usize) = usize::try_from(active_index) + { + sender.input(SerialMinSettingsWidgetMessage::PortChanged(active_index_usize)); + } + })); + + + let custom_port_input = gtk::Entry::builder() + .hexpand(true) + .placeholder_text(t!("serial_min.settings.custom_port_placeholder")) + .build(); + + root.append(&custom_port_input); + + + let custom_port_input_cloned = custom_port_input.clone(); + custom_port_input.connect_changed(clone!( + @strong sender => move |_| + { + sender.input(SerialMinSettingsWidgetMessage::CustomPortChanged(String::from(custom_port_input_cloned.text().as_str()))); + } + )); + + + let model = SerialMinSettingsWidget + { + ports, + + custom_port: String::new(), + custom_port_visible: true, + + tracker: 0 + }; + + + // TODO load settings + + + let widgets = SerialMinSettingsWidgets + { + custom_port_input + }; + + ComponentParts { model, widgets } + } + + + fn update(&mut self, msg: Self::Input, _sender: ComponentSender) + { + match msg + { + SerialMinSettingsWidgetMessage::PortChanged(index) => + { + self.set_custom_port_visible(index >= self.ports.len()); + }, + + SerialMinSettingsWidgetMessage::CustomPortChanged(value) => + { + self.set_custom_port(value); + } + } + } + + + fn update_view(&self, widgets: &mut Self::Widgets, _sender: ComponentSender) + { + if self.changed_custom_port_visible() + { + log::info!("Visible: {}", self.custom_port_visible); + widgets.custom_port_input.set_visible(self.custom_port_visible); + } + + if self.changed_custom_port() + { + // should this sync two-way or not? + //widgets + } + } +} + + +impl EmbeddedWidgetConnector for Connector +{ + fn root(&self) -> &relm4::gtk::Widget + { + self.widget().as_ref() + } +} \ No newline at end of file diff --git a/Linux/src/main.rs b/Linux/src/main.rs index d09e1e1..6b76d81 100644 --- a/Linux/src/main.rs +++ b/Linux/src/main.rs @@ -1,8 +1,4 @@ -use std::cell::RefCell; -use std::rc::Rc; use env_logger::Env; -use mainwindow::MainWindowInit; -use orchestrator::Orchestrator; use relm4::prelude::*; #[macro_use] @@ -12,6 +8,7 @@ i18n!("locales"); pub mod util; +pub mod ui; pub mod registry; pub mod devices; pub mod actions; @@ -28,13 +25,6 @@ fn main() relm4_icons::initialize_icons(); - let orchestrator = Rc::new(RefCell::new(Orchestrator::new())); - let app = RelmApp::new("com.github.mvrens.massiveknob"); - app.run::(MainWindowInit - { - orchestrator: Rc::clone(&orchestrator) - }); - - orchestrator.borrow_mut().finalize(); + app.run::(()); } \ No newline at end of file diff --git a/Linux/src/mainwindow.rs b/Linux/src/mainwindow.rs index 3cdf50e..4d387ff 100644 --- a/Linux/src/mainwindow.rs +++ b/Linux/src/mainwindow.rs @@ -1,59 +1,49 @@ -use std::cell::RefCell; -use std::rc::Rc; use gtk::glib::clone; use gtk::prelude::*; use relm4::prelude::*; use crate::orchestrator::Orchestrator; use crate::registry::RegistryItem; +use crate::ui::EmbeddedWidgetConnector; use crate::util::unique_id::UniqueId; +#[tracker::track] pub struct MainWindow { - orchestrator: Rc>, - devices_sorted: Vec -} + #[do_not_track] + orchestrator: Orchestrator, + #[do_not_track] + devices_sorted: Vec, -pub struct MainWindowInit -{ - pub orchestrator: Rc> -} - - -impl std::fmt::Debug for MainWindowInit -{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result - { - f.debug_struct("MainWindowInit") - // Skip orchestrator - .finish() - } + #[no_eq] + device_settings_widget: Option> } #[derive(Debug)] pub enum MainWindowMsg { + DeviceInitial(usize), DeviceChanged(usize) } pub struct MainWindowWidgets { - _device: MainWindowDeviceWidgets + device: MainWindowDeviceWidgets } pub struct MainWindowDeviceWidgets { - _devices_combobox: gtk::ComboBoxText + settings_container: gtk::Box } impl SimpleComponent for MainWindow { - type Init = MainWindowInit; + type Init = (); type Input = MainWindowMsg; type Output = (); type Root = gtk::Window; @@ -71,20 +61,14 @@ impl SimpleComponent for MainWindow } - fn init(data: Self::Init, window: Self::Root, sender: ComponentSender) -> ComponentParts + fn init(_data: Self::Init, window: Self::Root, sender: ComponentSender) -> ComponentParts { - { - let mut init_orchestrator = data.orchestrator.borrow_mut(); - init_orchestrator.initialize(); - } - - - let orchestrator = data.orchestrator.borrow(); + let orchestrator = Orchestrator::new(); let mut devices_sorted: Vec = orchestrator.devices() .map(|device| SortedDevice { - unique_id: device.unique_id(), + unique_id: device.unique_id.clone(), name: device.name() }) .collect(); @@ -94,8 +78,12 @@ impl SimpleComponent for MainWindow let model = MainWindow { - orchestrator: data.orchestrator.clone(), - devices_sorted + orchestrator, + devices_sorted, + + device_settings_widget: None, + + tracker: 0 }; let widgets = MainWindowBuilder::new(&window, &model, &sender).build(); @@ -108,18 +96,53 @@ impl SimpleComponent for MainWindow { match msg { - MainWindowMsg::DeviceChanged(index) => - { - let mut orchestrator = self.orchestrator.borrow_mut(); - let device = &self.devices_sorted[index]; + MainWindowMsg::DeviceInitial(index) => self.apply_device_settings_widget(index, true), + MainWindowMsg::DeviceChanged(index) => self.apply_device_settings_widget(index, false) + } + } - orchestrator.set_current_device_id(device.unique_id.clone()); + + fn update_view(&self, widgets: &mut Self::Widgets, _sender: ComponentSender) + { + if self.changed(MainWindow::device_settings_widget()) + { + let current_child = widgets.device.settings_container.last_child(); + if let Some(current_child) = ¤t_child + { + widgets.device.settings_container.remove(current_child); + } + + if let Some(new_child) = &self.device_settings_widget + { + widgets.device.settings_container.append(new_child.as_ref().root()); } } } } +impl MainWindow +{ + fn apply_device_settings_widget(&mut self, index: usize, initial: bool) + { + let mut widget = None; + { + if initial + { + self.orchestrator.with_active_device(|device_instance, cookie| { widget = device_instance.create_settings_widget(cookie) }); + } + else + { + let device_id = self.devices_sorted[index].unique_id.clone(); + self.orchestrator.set_active_device_id(&device_id, |device_instance, cookie| { widget = device_instance.create_settings_widget(cookie) }); + } + } + + self.set_device_settings_widget(widget) + } +} + + struct MainWindowBuilder<'a> { window: &'a gtk::Window, @@ -148,7 +171,7 @@ impl<'a> MainWindowBuilder<'a> MainWindowWidgets { - _device: self.init_device_tab(&tabs) + device: self.init_device_tab(&tabs) //Self::new_box_tab(&tabs, "mainwindow.tab.analoginputs"); //Self::new_box_tab(&tabs, "mainwindow.tab.digitalinputs"); //Self::add_box_tab(&tabs, "mainwindow.tab.analogoutputs"); @@ -171,11 +194,27 @@ impl<'a> MainWindowBuilder<'a> - let devices_combobox = gtk::ComboBoxText::builder() - .build(); + let devices_combobox = gtk::ComboBoxText::builder().build(); + tab.append(&devices_combobox); + + + let active_device_id = self.model.orchestrator.active_device_id(); + for (index, device) in self.model.devices_sorted.iter().enumerate() + { + devices_combobox.append_text(device.name.as_str()); + + if let Some(device_id) = &active_device_id + { + if *device_id == device.unique_id + { + devices_combobox.set_active(Some(index as u32)); + sender.input(MainWindowMsg::DeviceInitial(index)); + } + } + } + let devices_combobox_cloned = devices_combobox.clone(); - devices_combobox.connect_changed(clone!( @strong sender => move |_| { @@ -188,29 +227,14 @@ impl<'a> MainWindowBuilder<'a> } })); - tab.append(&devices_combobox); - - let current_device_id = self.model.orchestrator.borrow().current_device_id(); - - - for (index, device) in self.model.devices_sorted.iter().enumerate() - { - devices_combobox.append_text(device.name.as_str()); - - if let Some(device_id) = current_device_id.clone() - { - if device_id == device.unique_id - { - devices_combobox.set_active(Some(index as u32)); - } - } - } + let settings_container = gtk::Box::builder().build(); + tab.append(&settings_container); MainWindowDeviceWidgets { - _devices_combobox: devices_combobox + settings_container } } @@ -237,6 +261,7 @@ impl<'a> MainWindowBuilder<'a> } +#[derive(Clone)] struct SortedDevice { unique_id: UniqueId, diff --git a/Linux/src/orchestrator/mod.rs b/Linux/src/orchestrator/mod.rs index e3882ea..9ff4325 100644 --- a/Linux/src/orchestrator/mod.rs +++ b/Linux/src/orchestrator/mod.rs @@ -1,5 +1,8 @@ +use std::borrow::{Borrow, BorrowMut}; +use std::sync::Arc; + use crate::actions; -use crate::actions::ActionRegistryItem; +//use crate::actions::ActionRegistryItem; use crate::config::json::JsonConfigManager; use crate::config::{ConfigManager, ConfigName}; use crate::devices::{self, Device}; @@ -14,15 +17,13 @@ mod settings; pub struct Orchestrator { config_manager: ConfigManager, + settings_name: ConfigName, device_registry: Registry, - action_registry: Registry, + //action_registry: Registry, - settings_name: ConfigName, - settings: settings::Settings, - - - current_device_instance: Option> +// current_device_instance: Option> + active_device: Option } @@ -31,117 +32,151 @@ impl Orchestrator pub fn new() -> Self { let config_manager = ConfigManager::new(); - let settings_name = ConfigName::new("settings"); let settings = match config_manager.read_json(&settings_name).expect("Error reading settings") { - None => settings::Settings::new(), + None => settings::Settings::default(), Some(v) => v }; + let mut device_registry = Registry::new(); let mut action_registry = Registry::new(); devices::register(&mut device_registry); actions::register(&mut action_registry); - Self + let mut instance = Self { config_manager, + settings_name, device_registry, - action_registry, + //action_registry, - settings_name, - settings, + //settings, - current_device_instance: None + active_device: None + }; + + + instance.initialize(settings); + instance + } + + + fn initialize(&mut self, settings: settings::Settings) + { + if let Some(device_id) = settings.device_id + { + self.set_active_device(Some(UniqueId::from(device_id))); } } - pub fn initialize(&mut self) - { - self.set_current_device(self.current_device_id()); - } - - - pub fn finalize(&mut self) - { - self.set_current_device(None); - } - - pub fn devices(&self) -> impl Iterator { self.device_registry.iter() } - pub fn current_device_id(&self) -> Option + pub fn active_device_id(&self) -> Option { - let Some(device_id) = &self.settings.device_id else { return None }; - Some(UniqueId::new(device_id.as_str())) + let Some(active_device) = &self.active_device else { return None }; + Some(active_device.id.clone()) } + /* + pub fn current_device(&self) -> Option<&DeviceRegistryItem> { let Some(device_id) = self.current_device_id() else { return None }; self.device_registry.by_id(device_id) } + */ - pub fn set_current_device_id(&mut self, id: UniqueId) + pub fn with_active_device(&self, callback: F) where F: FnOnce(&dyn Device) { - let new_id = Some(String::from(id.as_str())); - if new_id == self.settings.device_id { return; } + let Some(active_device) = self.active_device else { return }; + let instance = active_device.instance.clone(); - self.settings.device_id = new_id; + callback(instance.as_ref().as_ref()); + + self.active_device.as_ref().map(|device| callback(&*device.instance.clone())); + } + + + + pub fn set_active_device_id(&mut self, id: &UniqueId, on_changed: F) where F: FnOnce(&dyn Device) + { + let id = Some(id.clone()); + if id == self.active_device_id() { return } + + self.set_active_device(id); self.store_settings(); - self.set_current_device(Some(id)); + self.with_active_device(on_changed); } + fn store_settings(&self) { - if let Err(e) = self.config_manager.write_json(&self.settings_name, &self.settings) + let settings = settings::Settings + { + device_id: match &self.active_device + { + None => None, + Some(v) => Some(v.id.clone().into()) + } + }; + + if let Err(e) = self.config_manager.write_json(&self.settings_name, &settings) { log::error!("Error writing settings: {e}"); } } - fn set_current_device(&mut self, id: Option) + + fn set_active_device(&mut self, id: Option) { - let prev_device_instance; - let new_device = match id + self.active_device = match id { None => None, - Some(v) => self.device_registry.by_id(v) + Some(v) => + match self.device_registry.by_id(&v) + { + None => None, + Some(d) => + { + Some(ActiveDevice + { + id: v.clone(), + instance: Arc::new((d.factory)()) + }) + } + } }; - - - // Replace the device instance - if let Some(new_device) = new_device - { - let mut new_device_instance = (new_device.factory)(); - new_device_instance.activate(); - - prev_device_instance = self.current_device_instance.replace(new_device_instance); - } - else - { - prev_device_instance = self.current_device_instance.take(); - } - - - // Deactivate the previous instance - if let Some(mut prev_device_instance) = prev_device_instance - { - prev_device_instance.deactivate(); - } } +} + + +impl Drop for Orchestrator +{ + fn drop(&mut self) + { + self.set_active_device(None); + } +} + + + +struct ActiveDevice +{ + id: UniqueId, + instance: Arc> } \ No newline at end of file diff --git a/Linux/src/orchestrator/settings.rs b/Linux/src/orchestrator/settings.rs index 1cc6c46..874f6c6 100644 --- a/Linux/src/orchestrator/settings.rs +++ b/Linux/src/orchestrator/settings.rs @@ -7,11 +7,11 @@ pub struct Settings } -impl Settings +impl Default for Settings { - pub fn new() -> Self + fn default() -> Self { - Self + Self { device_id: None } diff --git a/Linux/src/registry/mod.rs b/Linux/src/registry/mod.rs index 385683d..f2cf89b 100644 --- a/Linux/src/registry/mod.rs +++ b/Linux/src/registry/mod.rs @@ -49,7 +49,7 @@ impl Registry where T: RegistryItem } - pub fn by_id(&self, id: UniqueId) -> Option<&T> + pub fn by_id(&self, id: &UniqueId) -> Option<&T> { self.items.get(id.as_str()) } diff --git a/Linux/src/ui/mod.rs b/Linux/src/ui/mod.rs new file mode 100644 index 0000000..0c2b65b --- /dev/null +++ b/Linux/src/ui/mod.rs @@ -0,0 +1,4 @@ +pub trait EmbeddedWidgetConnector +{ + fn root(&self) -> &relm4::gtk::Widget; +} \ No newline at end of file diff --git a/Linux/src/util/validated_string.rs b/Linux/src/util/validated_string.rs index d571fa9..eb0314d 100644 --- a/Linux/src/util/validated_string.rs +++ b/Linux/src/util/validated_string.rs @@ -22,10 +22,10 @@ impl ValidatedString let pattern = Regex::new(T::pattern()).unwrap(); assert!(pattern.is_match(value), "Value '{value}' has invalid characters"); - Self - { - inner: value.to_string(), - _phantom: PhantomData + Self + { + inner: value.to_string(), + _phantom: PhantomData } } @@ -39,14 +39,41 @@ impl ValidatedString impl Clone for ValidatedString { - fn clone(&self) -> Self + fn clone(&self) -> Self { - Self + Self { inner: self.inner.clone(), _phantom: PhantomData } - } + } +} + + +impl From<&str> for ValidatedString +{ + fn from(value: &str) -> Self + { + Self::new(value) + } +} + + +impl From for ValidatedString +{ + fn from(value: String) -> Self + { + Self::new(value.as_str()) + } +} + + +impl Into for ValidatedString +{ + fn into(self) -> String + { + self.inner.clone() + } }