Skip to content

Commit 40c2bdc

Browse files
committed
autostart on MacOS
1 parent d8fbfde commit 40c2bdc

11 files changed

Lines changed: 164 additions & 35 deletions

File tree

‎new-ui/package.json‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
"@stablelib/base64": "^2.0.1",
2121
"@stablelib/x25519": "^2.0.1",
2222
"@tanstack/react-form": "^1.33.5",
23-
"@tauri-apps/plugin-autostart": "^2.7.0",
2423
"@tanstack/react-query": "^5.104.1",
2524
"@tanstack/react-router": "^1.170.41",
2625
"@tauri-apps/api": "^2.12.1",

‎new-ui/pnpm-lock.yaml‎

Lines changed: 0 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎new-ui/src/pages/full/SettingsPage/SettingsPage.tsx‎

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import './style.scss';
22
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
3-
import { disable, enable, isEnabled } from '@tauri-apps/plugin-autostart';
43
import { openUrl } from '@tauri-apps/plugin-opener';
54
import clsx from 'clsx';
65
import { type ReactNode, useMemo, useState } from 'react';
6+
import { getAutostartStatus, setAutostartEnabled } from '../../../shared/autostart';
77
import { Divider } from '../../../shared/components/Divider/Divider';
88
import { FullPageTitle } from '../../../shared/components/FullPageTitle/FullPageTitle';
99
import { Icon, IconKind } from '../../../shared/components/Icon';
@@ -38,12 +38,12 @@ export const SettingsPage = () => {
3838
const queryClient = useQueryClient();
3939
const { data: appConfig } = useQuery(getAppConfigQueryOptions);
4040
const {
41-
data: autostartEnabled,
41+
data: autostartStatus,
4242
isError: isAutostartStatusError,
4343
isPending: isAutostartStatusPending,
4444
} = useQuery({
4545
queryKey: AUTOSTART_QUERY_KEY,
46-
queryFn: isEnabled,
46+
queryFn: getAutostartStatus,
4747
});
4848

4949
const { mutate: patchConfig } = useMutation({
@@ -56,15 +56,13 @@ export const SettingsPage = () => {
5656
},
5757
});
5858

59-
const { mutate: setAutostart, isPending: isAutostartUpdating } = useMutation({
60-
mutationFn: async (enabled: boolean) => {
61-
if (enabled) {
62-
await enable();
63-
} else {
64-
await disable();
65-
}
66-
},
67-
onSuccess: () => queryClient.invalidateQueries({ queryKey: AUTOSTART_QUERY_KEY }),
59+
const {
60+
mutate: setAutostart,
61+
isPending: isAutostartUpdating,
62+
isError: isAutostartUpdateError,
63+
} = useMutation({
64+
mutationFn: setAutostartEnabled,
65+
onSettled: () => queryClient.invalidateQueries({ queryKey: AUTOSTART_QUERY_KEY }),
6866
onError: (error) => {
6967
console.error(error);
7068
},
@@ -172,14 +170,28 @@ export const SettingsPage = () => {
172170
<div className="sections startup-settings">
173171
<SettingRow
174172
title="Start Defguard automatically when your operating system starts"
173+
description={
174+
autostartStatus === 'requiresApproval'
175+
? 'Approve Defguard in System Settings > General > Login Items to start it at login.'
176+
: isAutostartStatusError ||
177+
isAutostartUpdateError ||
178+
autostartStatus === 'notFound'
179+
? 'Could not update or check login startup. Please try again.'
180+
: undefined
181+
}
175182
inline
176183
>
177184
<Toggle
178-
active={autostartEnabled ?? false}
185+
active={
186+
autostartStatus === 'enabled' || autostartStatus === 'requiresApproval'
187+
}
179188
disabled={
180-
isAutostartStatusPending || isAutostartStatusError || isAutostartUpdating
189+
isAutostartStatusPending ||
190+
isAutostartStatusError ||
191+
isAutostartUpdating ||
192+
autostartStatus === 'notFound'
181193
}
182-
onClick={() => setAutostart(!autostartEnabled)}
194+
onClick={() => setAutostart(autostartStatus === 'disabled')}
183195
/>
184196
</SettingRow>
185197
<SettingRow title="Start the app minimized to the system tray" inline>

‎new-ui/src/shared/autostart.ts‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { invoke } from '@tauri-apps/api/core';
2+
3+
export type AutostartStatus = 'enabled' | 'disabled' | 'requiresApproval' | 'notFound';
4+
5+
/** Read the OS login registration; no autostart preference is stored in AppConfig. */
6+
export const getAutostartStatus = (): Promise<AutostartStatus> =>
7+
invoke<AutostartStatus>('get_autostart_status');
8+
9+
/** Update the OS login registration and return its effective status. */
10+
export const setAutostartEnabled = (enabled: boolean): Promise<AutostartStatus> =>
11+
invoke<AutostartStatus>('set_autostart_enabled', { enabled });

‎src-tauri/Cargo.lock‎

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎src-tauri/Cargo.toml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ objc2-app-kit = "0.3"
185185
objc2-foundation = "0.3"
186186
objc2-network-extension = "0.3"
187187
objc2-system-extensions = { version = "0.3", optional = true }
188+
objc2-service-management = { version = "0.3.2", default-features = false, features = ["std", "objc2", "objc2-foundation", "SMAppService"] }
188189

189190
[target.'cfg(unix)'.dependencies]
190191
nix.workspace = true

‎src-tauri/capabilities/default.json‎

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,6 @@
4545
"core:window:allow-start-dragging",
4646
"core:window:allow-toggle-maximize",
4747
"core:webview:allow-print",
48-
"autostart:allow-disable",
49-
"autostart:allow-enable",
50-
"autostart:allow-is-enabled",
5148
"deep-link:default",
5249
"fs:default",
5350
"fs:allow-write-text-file",

‎src-tauri/permissions/default.toml‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ commands.allow = [
5252
"stop_global_logwatcher",
5353
"command_get_app_config",
5454
"command_set_app_config",
55+
"get_autostart_status",
56+
"set_autostart_enabled",
5557
"get_provisioning_config",
5658
"get_platform_header",
5759
"set_location_mfa_step_plan",

‎src-tauri/src/autostart.rs‎

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
use serde::Serialize;
2+
3+
/// Registration state reported by the operating system for launch at login.
4+
#[derive(Serialize)]
5+
#[serde(rename_all = "camelCase")]
6+
pub enum AutostartStatus {
7+
Enabled,
8+
Disabled,
9+
RequiresApproval,
10+
NotFound,
11+
}
12+
13+
#[cfg(target_os = "macos")]
14+
fn macos_service() -> objc2::rc::Retained<objc2_service_management::SMAppService> {
15+
// SAFETY: mainAppService is an Objective-C class method returning an owned object.
16+
unsafe { objc2_service_management::SMAppService::mainAppService() }
17+
}
18+
19+
#[cfg(target_os = "macos")]
20+
fn macos_status(service: &objc2_service_management::SMAppService) -> AutostartStatus {
21+
use objc2_service_management::SMAppServiceStatus;
22+
23+
// SAFETY: status reads the ServiceManagement registration for the main app.
24+
match unsafe { service.status() } {
25+
SMAppServiceStatus::Enabled => AutostartStatus::Enabled,
26+
SMAppServiceStatus::NotRegistered => AutostartStatus::Disabled,
27+
SMAppServiceStatus::RequiresApproval => AutostartStatus::RequiresApproval,
28+
_ => AutostartStatus::NotFound,
29+
}
30+
}
31+
32+
#[tauri::command]
33+
pub fn get_autostart_status(app: tauri::AppHandle) -> Result<AutostartStatus, String> {
34+
#[cfg(target_os = "macos")]
35+
{
36+
let _ = app;
37+
Ok(macos_status(&macos_service()))
38+
}
39+
#[cfg(not(target_os = "macos"))]
40+
{
41+
use tauri_plugin_autostart::ManagerExt;
42+
43+
app.autolaunch()
44+
.is_enabled()
45+
.map(|enabled| {
46+
if enabled {
47+
AutostartStatus::Enabled
48+
} else {
49+
AutostartStatus::Disabled
50+
}
51+
})
52+
.map_err(|error| error.to_string())
53+
}
54+
}
55+
56+
#[tauri::command]
57+
pub fn set_autostart_enabled(
58+
app: tauri::AppHandle,
59+
enabled: bool,
60+
) -> Result<AutostartStatus, String> {
61+
#[cfg(target_os = "macos")]
62+
{
63+
let _ = app;
64+
let service = macos_service();
65+
let status = macos_status(&service);
66+
if enabled && matches!(status, AutostartStatus::Disabled) {
67+
// SAFETY: ServiceManagement registers the calling signed main application.
68+
unsafe { service.registerAndReturnError() }
69+
.map_err(|error| error.localizedDescription().to_string())?;
70+
} else if !enabled
71+
&& matches!(
72+
status,
73+
AutostartStatus::Enabled | AutostartStatus::RequiresApproval
74+
)
75+
{
76+
// SAFETY: ServiceManagement unregisters only this main application.
77+
unsafe { service.unregisterAndReturnError() }
78+
.map_err(|error| error.localizedDescription().to_string())?;
79+
} else if matches!(status, AutostartStatus::NotFound) {
80+
return Err("Main app login service is unavailable".into());
81+
}
82+
Ok(macos_status(&service))
83+
}
84+
#[cfg(not(target_os = "macos"))]
85+
{
86+
use tauri_plugin_autostart::ManagerExt;
87+
88+
if enabled {
89+
app.autolaunch()
90+
.enable()
91+
.map_err(|error| error.to_string())?;
92+
} else {
93+
app.autolaunch()
94+
.disable()
95+
.map_err(|error| error.to_string())?;
96+
}
97+
get_autostart_status(app)
98+
}
99+
}

‎src-tauri/src/gui.rs‎

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ use crate::utils::sync_connections;
2626
use crate::{
2727
app_config::AppConfig,
2828
appstate::AppState,
29+
autostart,
2930
commands::*,
3031
database::{
3132
handle_db_migrations,
@@ -162,7 +163,7 @@ async fn startup(app_handle: &AppHandle) {
162163
pub fn run_app() {
163164
info!("Starting Defguard client version {VERSION}");
164165

165-
let app = Builder::default()
166+
let builder = Builder::default()
166167
.invoke_handler(tauri::generate_handler![
167168
all_locations,
168169
has_any_visible_locations,
@@ -190,6 +191,8 @@ pub fn run_app() {
190191
stop_global_logwatcher,
191192
command_get_app_config,
192193
command_set_app_config,
194+
autostart::get_autostart_status,
195+
autostart::set_autostart_enabled,
193196
get_provisioning_config,
194197
get_platform_header,
195198
get_posture_data,
@@ -255,11 +258,12 @@ pub fn run_app() {
255258
if !is_deep_link {
256259
show_tray_or_full_view(app);
257260
}
258-
}))
259-
.plugin(tauri_plugin_autostart::init(
260-
tauri_plugin_autostart::MacosLauncher::LaunchAgent,
261-
None,
262-
))
261+
}));
262+
263+
#[cfg(not(target_os = "macos"))]
264+
let builder = builder.plugin(tauri_plugin_autostart::Builder::new().build());
265+
266+
let app = builder
263267
.plugin(tauri_plugin_deep_link::init())
264268
.plugin(tauri_plugin_dialog::init())
265269
.plugin(tauri_plugin_clipboard_manager::init())

0 commit comments

Comments
 (0)