Skip to content

Commit

Permalink
feat: implement config reloading; add option to tray menu
Browse files Browse the repository at this point in the history
  • Loading branch information
lars-berger committed Sep 15, 2024
1 parent 71ced0b commit 473f5e8
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 54 deletions.
10 changes: 10 additions & 0 deletions packages/desktop/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,10 @@ pub struct Config {
_settings_change_rx: broadcast::Receiver<SettingsConfig>,

pub settings_change_tx: broadcast::Sender<SettingsConfig>,

_window_configs_change_rx: broadcast::Receiver<Vec<WindowConfigEntry>>,

pub window_configs_change_tx: broadcast::Sender<Vec<WindowConfigEntry>>,
}

#[derive(Clone, Debug)]
Expand Down Expand Up @@ -166,7 +170,10 @@ impl Config {

let settings = Self::read_settings_or_init(app_handle, &config_dir)?;
let window_configs = Self::read_window_configs(&config_dir)?;

let (settings_change_tx, _settings_change_rx) = broadcast::channel(16);
let (window_configs_change_tx, _window_configs_change_rx) =
broadcast::channel(16);

Ok(Self {
app_handle: app_handle.clone(),
Expand All @@ -175,6 +182,8 @@ impl Config {
window_configs: Arc::new(Mutex::new(window_configs)),
_settings_change_rx,
settings_change_tx,
_window_configs_change_rx,
window_configs_change_tx,
})
}

Expand All @@ -195,6 +204,7 @@ impl Config {
}

self.settings_change_tx.send(new_settings)?;
self.window_configs_change_tx.send(new_window_configs)?;

Ok(())
}
Expand Down
78 changes: 67 additions & 11 deletions packages/desktop/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
#![feature(async_closure)]
#![feature(iterator_try_collect)]

use std::{env, sync::Arc, time::Duration};
use std::{env, sync::Arc};

use anyhow::Context;
use clap::Parser;
use tauri::{async_runtime::block_on, Manager};
use tokio::{task, time::sleep};
use tracing::{error, level_filters::LevelFilter};
use tauri::{async_runtime::block_on, Manager, RunEvent};
use tokio::task;
use tracing::{error, info, level_filters::LevelFilter};
use tracing_subscriber::EnvFilter;

use crate::{
Expand Down Expand Up @@ -86,7 +86,7 @@ fn start_app(cli: Cli) -> anyhow::Result<()> {

tauri::async_runtime::set(tokio::runtime::Handle::current());

tauri::Builder::default()
let app = tauri::Builder::default()
.setup(move |app| {
task::block_in_place(|| {
block_on(async move {
Expand All @@ -106,8 +106,11 @@ fn start_app(cli: Cli) -> anyhow::Result<()> {
app.manage(monitor_state.clone());

// Initialize `WindowFactory` in Tauri state.
let window_factory =
Arc::new(WindowFactory::new(app.handle(), monitor_state));
let window_factory = Arc::new(WindowFactory::new(
app.handle(),
config.clone(),
monitor_state,
));
app.manage(window_factory.clone());

// If this is not the first instance of the app, this will emit
Expand Down Expand Up @@ -145,9 +148,14 @@ fn start_app(cli: Cli) -> anyhow::Result<()> {
.await?;

// Add application icon to system tray.
let _ =
SysTray::new(app.handle(), config.clone(), window_factory)
.await?;
let tray = SysTray::new(
app.handle(),
config.clone(),
window_factory.clone(),
)
.await?;

listen_events(window_factory, config, tray);

Ok(())
})
Expand All @@ -161,11 +169,59 @@ fn start_app(cli: Cli) -> anyhow::Result<()> {
commands::set_always_on_top,
commands::set_skip_taskbar
])
.run(tauri::generate_context!())?;
.build(tauri::generate_context!())?;

app.run(|_, event| {
if let RunEvent::ExitRequested { code, api, .. } = &event {
if code.is_none() {
// Keep the message loop running even if all windows are closed.
api.prevent_exit();
}
}
});

Ok(())
}

fn listen_events(
window_factory: Arc<WindowFactory>,
config: Arc<Config>,
tray: Arc<SysTray>,
) {
let mut window_open_rx = window_factory.open_tx.subscribe();
let mut window_close_rx = window_factory.close_tx.subscribe();
let mut settings_change_rx = config.settings_change_tx.subscribe();
let mut window_configs_change_rx =
config.window_configs_change_tx.subscribe();

task::spawn(async move {
loop {
let res = tokio::select! {
Ok(_) = window_open_rx.recv() => {
info!("Window opened.");
tray.refresh().await
},
Ok(_) = window_close_rx.recv() => {
info!("Window closed.");
tray.refresh().await
},
Ok(_) = settings_change_rx.recv() => {
info!("Settings changed.");
tray.refresh().await
},
Ok(_) = window_configs_change_rx.recv() => {
info!("Window configs changed.");
window_factory.relaunch().await
},
};

if let Err(err) = res {
error!("{:?}", err);
}
}
});
}

/// Setup single instance Tauri plugin.
fn setup_single_instance(
app: &tauri::App,
Expand Down
60 changes: 19 additions & 41 deletions packages/desktop/src/sys_tray.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use crate::{
#[derive(Debug, Clone)]
enum MenuEvent {
ShowConfigFolder,
ReloadConfigs,
Exit,
ToggleWindowConfig { enable: bool, path: PathBuf },
ToggleStartupWindowConfig { enable: bool, path: PathBuf },
Expand All @@ -28,6 +29,7 @@ impl ToString for MenuEvent {
fn to_string(&self) -> String {
match self {
MenuEvent::ShowConfigFolder => "show_config_folder".to_string(),
MenuEvent::ReloadConfigs => "reload_configs".to_string(),
MenuEvent::Exit => "exit".to_string(),
MenuEvent::ToggleWindowConfig { enable, path } => {
format!(
Expand Down Expand Up @@ -55,6 +57,7 @@ impl FromStr for MenuEvent {

match parts.as_slice() {
["show", "config", "folder"] => Ok(Self::ShowConfigFolder),
["reload", "configs"] => Ok(Self::ReloadConfigs),
["exit"] => Ok(Self::Exit),
["toggle", "window", "config", enable @ ("true" | "false"), path @ ..] => {
Ok(Self::ToggleWindowConfig {
Expand Down Expand Up @@ -97,10 +100,7 @@ impl SysTray {

sys_tray.tray_icon = Some(sys_tray.create_tray_icon().await?);

let sys_tray = Arc::new(sys_tray);
sys_tray.clone().start_listener();

Ok(sys_tray)
Ok(Arc::new(sys_tray))
}

async fn create_tray_icon(&self) -> anyhow::Result<TrayIcon> {
Expand Down Expand Up @@ -142,43 +142,15 @@ impl SysTray {
Ok(tray_icon)
}

fn start_listener(self: Arc<Self>) {
let mut window_open_rx = self.window_factory.open_tx.subscribe();
let mut window_close_rx = self.window_factory.close_tx.subscribe();
let mut settings_change_rx =
self.config.settings_change_tx.subscribe();

tokio::spawn(async move {
loop {
let event = tokio::select! {
Ok(_) = window_open_rx.recv() => "Window open",
Ok(_) = window_close_rx.recv() => "Window close",
Ok(_) = settings_change_rx.recv() => "Settings change",
};

info!("{} received in system tray. Updating menu.", event);

// Drain receiver channels if multiple events are queued up.
window_open_rx = window_open_rx.resubscribe();
window_close_rx = window_close_rx.resubscribe();
settings_change_rx = settings_change_rx.resubscribe();

if let Some(tray_icon) = self.tray_icon.as_ref() {
if let Err(err) =
self.create_tray_menu().await.and_then(|menu| {
tray_icon
.set_menu(Some(menu))
.context("Failed to set tray menu.")
})
{
error!(
"Failed to update tray menu after {} event: {:?}",
event, err
);
}
}
}
});
pub async fn refresh(&self) -> anyhow::Result<()> {
info!("Updating system tray menu.");

if let Some(tray_icon) = self.tray_icon.as_ref() {
let tray_menu = self.create_tray_menu().await?;
tray_icon.set_menu(Some(tray_menu))?;
}

Ok(())
}

/// Returns the image to use for the system tray icon.
Expand Down Expand Up @@ -209,6 +181,7 @@ impl SysTray {
let mut tray_menu = MenuBuilder::new(&self.app_handle)
.item(&configs_menu)
.text(MenuEvent::ShowConfigFolder, "Show config folder")
.text(MenuEvent::ReloadConfigs, "Reload configs")
.separator();

// Add submenus for currently active windows.
Expand Down Expand Up @@ -251,6 +224,11 @@ impl SysTray {
.open_config_dir()
.context("Failed to open config folder.")
}
MenuEvent::ReloadConfigs => {
info!("Opening config folder from system tray.");

config.reload().await
}
MenuEvent::Exit => {
info!("Exiting through system tray.");

Expand Down
31 changes: 29 additions & 2 deletions packages/desktop/src/window_factory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use tracing::{error, info};

use crate::{
common::{PathExt, WindowExt},
config::{WindowAnchor, WindowConfig, WindowConfigEntry},
config::{Config, WindowAnchor, WindowConfig, WindowConfigEntry},
monitor_state::MonitorState,
};

Expand All @@ -34,11 +34,16 @@ pub struct WindowFactory {

pub close_tx: broadcast::Sender<WindowState>,

/// Reference to `Config`.
config: Arc<Config>,

_open_rx: broadcast::Receiver<WindowState>,

pub open_tx: broadcast::Sender<WindowState>,

/// Reference to `MonitorState` for window positioning.
/// Reference to `MonitorState`.
///
/// Used for window positioning.
monitor_state: Arc<MonitorState>,

/// Running total of windows created.
Expand Down Expand Up @@ -72,6 +77,7 @@ impl WindowFactory {
/// Creates a new `WindowFactory` instance.
pub fn new(
app_handle: &AppHandle,
config: Arc<Config>,
monitor_state: Arc<MonitorState>,
) -> Self {
let (open_tx, _open_rx) = broadcast::channel(16);
Expand All @@ -81,6 +87,7 @@ impl WindowFactory {
app_handle: app_handle.clone(),
_close_rx,
close_tx,
config,
_open_rx,
open_tx,
monitor_state,
Expand All @@ -89,6 +96,24 @@ impl WindowFactory {
}
}

pub async fn relaunch(&self) -> anyhow::Result<()> {
let window_states = self.states_by_config_path().await;

for (config_path, _) in window_states {
let _ = self.close_by_path(&config_path).await;

let window_config = self
.config
.window_config_by_path(&config_path)
.await?
.context("Window config not found.")?;

self.open(window_config).await?;
}

Ok(())
}

/// Opens windows from a given config entry.
pub async fn open(
&self,
Expand Down Expand Up @@ -186,6 +211,8 @@ impl WindowFactory {
Ok(())
}

// fn build_window(&self, )

/// Registers window events for a given window.
fn register_window_events(
&self,
Expand Down

0 comments on commit 473f5e8

Please sign in to comment.