From 0ec417e422cacfeef353b350d534a56e9510b81d Mon Sep 17 00:00:00 2001 From: unsecretised Date: Sat, 28 Mar 2026 03:01:34 +0800 Subject: [PATCH 1/2] add a autostart on login feature --- Cargo.lock | 25 +++++++++++++++++++++++++ Cargo.toml | 1 + src/app.rs | 1 + src/app/menubar.rs | 31 +++++++++++++++++++++++++++++++ src/app/pages/settings.rs | 11 +++++++++++ src/app/tile/update.rs | 12 ++++++++++++ src/config.rs | 2 ++ src/platform/macos/login.rs | 0 src/platform/macos/mod.rs | 16 ++++++++++++++++ 9 files changed, 99 insertions(+) create mode 100644 src/platform/macos/login.rs diff --git a/Cargo.lock b/Cargo.lock index bba2fcb..c059a04 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3092,6 +3092,30 @@ dependencies = [ "objc2-foundation 0.3.2", ] +[[package]] +name = "objc2-security" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe137109bd1e8b5a99390f77a7d8b2961dafc1a1c5db8f2e60329ad6d895a" +dependencies = [ + "bitflags 2.11.0", + "objc2 0.6.3", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-service-management" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b213642d6959cc6023ceb1217aa595eaaf09b8094ce95127c103cab611fe65e8" +dependencies = [ + "block2 0.6.2", + "objc2 0.6.3", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "objc2-security", +] + [[package]] name = "objc2-symbols" version = "0.2.2" @@ -3844,6 +3868,7 @@ dependencies = [ "objc2-application-services", "objc2-core-foundation", "objc2-foundation 0.3.2", + "objc2-service-management", "once_cell", "rand", "rayon", diff --git a/Cargo.toml b/Cargo.toml index a996831..82cb240 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ objc2-application-services = { version = "0.3.2", default-features = false, feat ] } objc2-core-foundation = "0.3.2" objc2-foundation = { version = "0.3.2", features = ["NSString"] } +objc2-service-management = "0.3.2" once_cell = "1.21.3" rand = "0.9.2" rayon = "1.11.0" diff --git a/src/app.rs b/src/app.rs index dc90071..516bb37 100644 --- a/src/app.rs +++ b/src/app.rs @@ -81,6 +81,7 @@ pub enum Editable { pub enum Message { WriteConfig(bool), SaveRanking, + ToggleAutoStartup(bool), LoadRanking, ToggleFavouriteApp(String), UpdateAvailable, diff --git a/src/app/menubar.rs b/src/app/menubar.rs index b9a566f..3edd2d5 100644 --- a/src/app/menubar.rs +++ b/src/app/menubar.rs @@ -63,6 +63,7 @@ pub fn menu_builder(config: Config, sender: ExtSender, update_item: bool) -> Men &about_item(get_image()), &open_github_item(), &PredefinedMenuItem::separator(), + &auto_start_item(config.start_at_login), &refresh_item(), &open_item(hotkey), &mode_item(modes), @@ -113,6 +114,22 @@ fn init_event_handler(sender: ExtSender, hotkey_id: Option) { }); } } + "auto_start_true" => { + runtime.spawn(async move { + sender + .clone() + .try_send(Message::ToggleAutoStartup(true)) + .unwrap(); + }); + } + "auto_start_false" => { + runtime.spawn(async move { + sender + .clone() + .try_send(Message::ToggleAutoStartup(false)) + .unwrap(); + }); + } "update" => { open_url("https://github.com/RustCastLabs/rustcast/releases/latest"); } @@ -156,6 +173,20 @@ fn discord_item() -> MenuItem { MenuItem::with_id("open_discord", "RustCast discord", true, None) } +fn auto_start_item(enabled: bool) -> MenuItem { + let id = match enabled { + true => "auto_start_true", + false => "auto_start_false", + }; + + let msg = match enabled { + true => "Don't open on login", + false => "Open on login", + }; + + MenuItem::with_id(id, msg, true, None) +} + fn hide_tray_icon() -> MenuItem { MenuItem::with_id("hide_tray_icon", "Hide Tray Icon", true, None) } diff --git a/src/app/pages/settings.rs b/src/app/pages/settings.rs index 71742ba..c9cdbd7 100644 --- a/src/app/pages/settings.rs +++ b/src/app/pages/settings.rs @@ -102,6 +102,16 @@ pub fn settings_page(config: Config) -> Element<'static, Message> { ), ]); + let theme_clone = theme.clone(); + let start_at_login = settings_item_row([ + settings_hint_text(theme.clone(), "Start at login"), + checkbox(config.clone().start_at_login) + .style(move |_, _| settings_checkbox_style(&theme_clone)) + .on_toggle(Message::ToggleAutoStartup) + .into(), + notice_item(theme.clone(), "If you want rustcast to start on login"), + ]); + let theme_clone = theme.clone(); let haptic = Row::from_iter([ settings_hint_text(theme.clone(), "Haptic feedback"), @@ -394,6 +404,7 @@ pub fn settings_page(config: Config) -> Element<'static, Message> { placeholder_setting.into(), search.into(), debounce.into(), + start_at_login.into(), haptic.into(), tray_icon.into(), auto_suggest.into(), diff --git a/src/app/tile/update.rs b/src/app/tile/update.rs index 8cac396..052059c 100644 --- a/src/app/tile/update.rs +++ b/src/app/tile/update.rs @@ -34,6 +34,7 @@ use crate::commands::Function; use crate::config::Config; use crate::config::MainPage; use crate::debounce::DebouncePolicy; +use crate::platform::macos::{start_at_login, stop_at_login}; use crate::quit::get_open_apps; use crate::unit_conversion; use crate::utils::is_valid_url; @@ -96,6 +97,17 @@ pub fn handle_update(tile: &mut Tile, message: Message) -> Task { Task::none() } + Message::ToggleAutoStartup(set_to) => { + if set_to { + start_at_login(); + tile.config.start_at_login = true + } else { + stop_at_login(); + tile.config.start_at_login = false + } + Task::done(Message::ReloadConfig) + } + Message::EscKeyPressed(id) => { if !tile.query_lc.is_empty() { return Task::batch([ diff --git a/src/config.rs b/src/config.rs index 667fa76..2a3f506 100644 --- a/src/config.rs +++ b/src/config.rs @@ -21,6 +21,7 @@ pub struct Config { pub clipboard_hotkey: String, pub buffer_rules: Buffer, pub main_page: MainPage, + pub start_at_login: bool, pub theme: Theme, pub placeholder: String, pub search_url: String, @@ -42,6 +43,7 @@ impl Default for Config { clipboard_hotkey: "SUPER+SHIFT+C".to_string(), buffer_rules: Buffer::default(), theme: Theme::default(), + start_at_login: true, placeholder: String::from("Time to be productive!"), search_url: "https://duckduckgo.com/search?q=%s".to_string(), haptic_feedback: false, diff --git a/src/platform/macos/login.rs b/src/platform/macos/login.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/platform/macos/mod.rs b/src/platform/macos/mod.rs index e8a8856..d075248 100644 --- a/src/platform/macos/mod.rs +++ b/src/platform/macos/mod.rs @@ -7,6 +7,22 @@ use iced::wgpu::rwh::WindowHandle; pub(super) use self::discovery::get_installed_apps; pub(super) use self::haptics::perform_haptic; +use objc2_service_management::SMAppService; + +pub fn start_at_login() { + unsafe { + SMAppService::mainAppService().registerAndReturnError().ok(); + } +} + +pub fn stop_at_login() { + unsafe { + SMAppService::mainAppService() + .unregisterAndReturnError() + .ok(); + } +} + /// This sets the activation policy of the app to Accessory, allowing rustcast to be visible ontop /// of fullscreen apps pub(super) fn set_activation_policy_accessory() { From 73313fb672405b171ec7438f1330c0770d64edfc Mon Sep 17 00:00:00 2001 From: unsecretised Date: Sat, 28 Mar 2026 03:09:33 +0800 Subject: [PATCH 2/2] remove from menu bar since it gets too cluttered --- src/app/menubar.rs | 31 ------------------------------- src/app/tile/update.rs | 2 +- src/main.rs | 5 ++++- src/platform/macos/mod.rs | 9 +++++++++ 4 files changed, 14 insertions(+), 33 deletions(-) diff --git a/src/app/menubar.rs b/src/app/menubar.rs index 3edd2d5..b9a566f 100644 --- a/src/app/menubar.rs +++ b/src/app/menubar.rs @@ -63,7 +63,6 @@ pub fn menu_builder(config: Config, sender: ExtSender, update_item: bool) -> Men &about_item(get_image()), &open_github_item(), &PredefinedMenuItem::separator(), - &auto_start_item(config.start_at_login), &refresh_item(), &open_item(hotkey), &mode_item(modes), @@ -114,22 +113,6 @@ fn init_event_handler(sender: ExtSender, hotkey_id: Option) { }); } } - "auto_start_true" => { - runtime.spawn(async move { - sender - .clone() - .try_send(Message::ToggleAutoStartup(true)) - .unwrap(); - }); - } - "auto_start_false" => { - runtime.spawn(async move { - sender - .clone() - .try_send(Message::ToggleAutoStartup(false)) - .unwrap(); - }); - } "update" => { open_url("https://github.com/RustCastLabs/rustcast/releases/latest"); } @@ -173,20 +156,6 @@ fn discord_item() -> MenuItem { MenuItem::with_id("open_discord", "RustCast discord", true, None) } -fn auto_start_item(enabled: bool) -> MenuItem { - let id = match enabled { - true => "auto_start_true", - false => "auto_start_false", - }; - - let msg = match enabled { - true => "Don't open on login", - false => "Open on login", - }; - - MenuItem::with_id(id, msg, true, None) -} - fn hide_tray_icon() -> MenuItem { MenuItem::with_id("hide_tray_icon", "Hide Tray Icon", true, None) } diff --git a/src/app/tile/update.rs b/src/app/tile/update.rs index 052059c..a65e26c 100644 --- a/src/app/tile/update.rs +++ b/src/app/tile/update.rs @@ -105,7 +105,7 @@ pub fn handle_update(tile: &mut Tile, message: Message) -> Task { stop_at_login(); tile.config.start_at_login = false } - Task::done(Message::ReloadConfig) + Task::none() } Message::EscKeyPressed(id) => { diff --git a/src/main.rs b/src/main.rs index 5497ea2..6b06a7c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,6 +17,7 @@ use std::{fs::OpenOptions, path::Path}; use crate::{ app::tile::{self, Tile}, config::Config, + platform::macos::get_autostart_status, }; use global_hotkey::{ @@ -43,11 +44,13 @@ fn main() -> iced::Result { .unwrap(); } - let config: Config = match std::fs::read_to_string(&file_path) { + let mut config: Config = match std::fs::read_to_string(&file_path) { Ok(a) => toml::from_str(&a).unwrap_or(Config::default()), Err(_) => Config::default(), }; + config.start_at_login = get_autostart_status(); + if cfg!(debug_assertions) { let sub = tracing_subscriber::fmt().finish(); EnvFilter::new("rustcast=info").with_subscriber(sub).init(); diff --git a/src/platform/macos/mod.rs b/src/platform/macos/mod.rs index d075248..ab7c05c 100644 --- a/src/platform/macos/mod.rs +++ b/src/platform/macos/mod.rs @@ -23,6 +23,15 @@ pub fn stop_at_login() { } } +pub fn get_autostart_status() -> bool { + unsafe { + SMAppService::mainAppService() + .registerAndReturnError() + .ok() + .is_some() + } +} + /// This sets the activation policy of the app to Accessory, allowing rustcast to be visible ontop /// of fullscreen apps pub(super) fn set_activation_policy_accessory() {