Skip to content

Commit fd3fe18

Browse files
authored
Merge pull request #36 from murdercode/hide-mac-icon
feat: add 'Hide Icon on Close' setting and related functionality
2 parents d4275ff + a9016c8 commit fd3fe18

File tree

6 files changed

+222
-4
lines changed

6 files changed

+222
-4
lines changed

src-tauri/Cargo.lock

Lines changed: 56 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: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,6 @@ base64 = "0.21"
5151
core-graphics = "0.23"
5252
core-foundation = "0.9"
5353
libc = "0.2"
54+
cocoa = "0.25"
55+
objc = "0.2"
5456

src-tauri/src/lib.rs

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ struct AppSettings {
8787
autostart: bool,
8888
#[serde(default = "default_analytics_enabled")]
8989
analytics_enabled: bool,
90+
#[serde(default)]
91+
hide_icon_on_close: bool,
9092
}
9193

9294
#[derive(Serialize, Deserialize, Clone)]
@@ -174,6 +176,7 @@ impl Default for AppSettings {
174176
advanced: AdvancedSettings::default(),
175177
autostart: false, // default to disabled
176178
analytics_enabled: true, // default to enabled
179+
hide_icon_on_close: false, // default to disabled
177180
}
178181
}
179182
}
@@ -612,6 +615,22 @@ async fn update_tray_icon(
612615
#[tauri::command]
613616
async fn show_window(app: AppHandle) -> Result<(), String> {
614617
if let Some(window) = app.get_webview_window("main") {
618+
// Check if hide_icon_on_close is enabled to restore dock visibility
619+
match load_settings(app.clone()).await {
620+
Ok(settings) => {
621+
if settings.hide_icon_on_close {
622+
// Restore dock visibility when showing window
623+
#[cfg(target_os = "macos")]
624+
{
625+
let _ = set_dock_visibility(app.clone(), true).await;
626+
}
627+
}
628+
}
629+
Err(_) => {
630+
// Ignore error, just proceed with showing window
631+
}
632+
}
633+
615634
window
616635
.show()
617636
.map_err(|e| format!("Failed to show window: {}", e))?;
@@ -929,7 +948,8 @@ pub fn run() {
929948
save_session_tags,
930949
add_session_tag,
931950
write_excel_file,
932-
start_oauth_server
951+
start_oauth_server,
952+
set_dock_visibility
933953
])
934954
.setup(|app| {
935955
// Track app started event (if enabled)
@@ -1023,9 +1043,42 @@ pub fn run() {
10231043
.build(app)?;
10241044

10251045
if let Some(window) = app.get_webview_window("main") {
1046+
let app_handle_for_close = app.handle().clone();
10261047
window.on_window_event(move |event| {
10271048
if let tauri::WindowEvent::CloseRequested { api, .. } = event {
1049+
// Always prevent close
10281050
api.prevent_close();
1051+
1052+
// Check if we should hide the app icon
1053+
let app_handle_clone = app_handle_for_close.clone();
1054+
tauri::async_runtime::spawn(async move {
1055+
match load_settings(app_handle_clone.clone()).await {
1056+
Ok(settings) => {
1057+
if settings.hide_icon_on_close {
1058+
// Hide the window and set app as dock hidden
1059+
if let Some(window) = app_handle_clone.get_webview_window("main") {
1060+
let _ = window.hide();
1061+
// Use macOS specific API to hide from dock
1062+
#[cfg(target_os = "macos")]
1063+
{
1064+
let _ = set_dock_visibility(app_handle_clone.clone(), false).await;
1065+
}
1066+
}
1067+
} else {
1068+
// Just hide the window without hiding from dock
1069+
if let Some(window) = app_handle_clone.get_webview_window("main") {
1070+
let _ = window.hide();
1071+
}
1072+
}
1073+
}
1074+
Err(_) => {
1075+
// Default behavior: just hide the window
1076+
if let Some(window) = app_handle_clone.get_webview_window("main") {
1077+
let _ = window.hide();
1078+
}
1079+
}
1080+
}
1081+
});
10291082
}
10301083
});
10311084
}
@@ -1283,3 +1336,40 @@ async fn start_oauth_server(window: tauri::Window) -> Result<u16, String> {
12831336
})
12841337
.map_err(|err| err.to_string())
12851338
}
1339+
1340+
#[tauri::command]
1341+
async fn set_dock_visibility(app: AppHandle, visible: bool) -> Result<(), String> {
1342+
#[cfg(target_os = "macos")]
1343+
{
1344+
app.run_on_main_thread(move || {
1345+
set_dock_visibility_native(visible);
1346+
})
1347+
.map_err(|e| format!("Failed to run on main thread: {}", e))?;
1348+
}
1349+
1350+
#[cfg(not(target_os = "macos"))]
1351+
{
1352+
return Err("Dock visibility is only supported on macOS".to_string());
1353+
}
1354+
1355+
Ok(())
1356+
}
1357+
1358+
#[cfg(target_os = "macos")]
1359+
fn set_dock_visibility_native(visible: bool) {
1360+
use cocoa::appkit::{NSApp, NSApplication, NSApplicationActivationPolicy};
1361+
use cocoa::base::nil;
1362+
1363+
unsafe {
1364+
let app = NSApp();
1365+
if app != nil {
1366+
let policy = if visible {
1367+
NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular
1368+
} else {
1369+
NSApplicationActivationPolicy::NSApplicationActivationPolicyAccessory
1370+
};
1371+
1372+
app.setActivationPolicy_(policy);
1373+
}
1374+
}
1375+
}

src/index.html

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -797,6 +797,15 @@ <h3>System Integration</h3>
797797
start
798798
minimized to the system tray.</p>
799799
</div>
800+
801+
<div class="setting-item">
802+
<label class="checkbox-label">
803+
<input type="checkbox" id="hide-icon-on-close">
804+
<span class="checkmark"></span>
805+
Hide Icon on Close
806+
</label>
807+
<p class="setting-description">Hide the app icon from the dock when closing the window with X. The app will continue running in the system tray.</p>
808+
</div>
800809
</div>
801810

802811
<div class="settings-section">

src/managers/settings-manager.js

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,8 @@ export class SettingsManager {
8383
appearance: { ...defaultSettings.appearance, ...loadedSettings.appearance },
8484
advanced: { ...defaultSettings.advanced, ...loadedSettings.advanced },
8585
autostart: loadedSettings.autostart !== undefined ? loadedSettings.autostart : defaultSettings.autostart,
86-
analytics_enabled: loadedSettings.analytics_enabled !== undefined ? loadedSettings.analytics_enabled : defaultSettings.analytics_enabled
86+
analytics_enabled: loadedSettings.analytics_enabled !== undefined ? loadedSettings.analytics_enabled : defaultSettings.analytics_enabled,
87+
hide_icon_on_close: loadedSettings.hide_icon_on_close !== undefined ? loadedSettings.hide_icon_on_close : defaultSettings.hide_icon_on_close
8788
};
8889
}
8990

@@ -118,7 +119,8 @@ export class SettingsManager {
118119
debug_mode: false // Debug mode with 3-second timers
119120
},
120121
autostart: false, // default to disabled
121-
analytics_enabled: true // Analytics enabled by default
122+
analytics_enabled: true, // Analytics enabled by default
123+
hide_icon_on_close: false // Hide icon on close disabled by default
122124
};
123125
}
124126

@@ -198,6 +200,9 @@ export class SettingsManager {
198200

199201
// Populate analytics setting
200202
this.loadAnalyticsSetting();
203+
204+
// Populate hide icon on close setting
205+
this.loadHideIconOnCloseSetting();
201206
}
202207

203208
setupEventListeners() {
@@ -860,6 +865,62 @@ export class SettingsManager {
860865
}
861866
}
862867

868+
async loadHideIconOnCloseSetting() {
869+
try {
870+
// Get current hide icon on close setting from our stored settings
871+
const hideIconOnClose = this.settings.hide_icon_on_close;
872+
873+
const checkbox = document.getElementById('hide-icon-on-close');
874+
if (checkbox) {
875+
checkbox.checked = hideIconOnClose;
876+
877+
// Setup event listener for the hide icon on close checkbox
878+
checkbox.addEventListener('change', async (e) => {
879+
await this.toggleHideIconOnClose(e.target.checked);
880+
});
881+
}
882+
} catch (error) {
883+
console.error('Failed to load hide icon on close setting:', error);
884+
// Default to disabled if we can't check the status
885+
const checkbox = document.getElementById('hide-icon-on-close');
886+
if (checkbox) {
887+
checkbox.checked = false;
888+
checkbox.addEventListener('change', async (e) => {
889+
await this.toggleHideIconOnClose(e.target.checked);
890+
});
891+
}
892+
}
893+
}
894+
895+
async toggleHideIconOnClose(enabled) {
896+
try {
897+
// Update our settings
898+
this.settings.hide_icon_on_close = enabled;
899+
900+
// Show user feedback
901+
if (enabled) {
902+
console.log('Hide icon on close enabled');
903+
NotificationUtils.showNotificationPing('✓ Hide icon on close enabled - App will hide from dock when closed', 'success');
904+
} else {
905+
console.log('Hide icon on close disabled');
906+
NotificationUtils.showNotificationPing('✓ Hide icon on close disabled - App will remain visible in dock', 'success');
907+
}
908+
909+
// Schedule auto-save to persist the setting
910+
this.scheduleAutoSave();
911+
912+
} catch (error) {
913+
console.error('Failed to toggle hide icon on close:', error);
914+
NotificationUtils.showNotificationPing('❌ Failed to toggle hide icon on close: ' + error, 'error');
915+
916+
// Revert the checkbox state on error
917+
const checkbox = document.getElementById('hide-icon-on-close');
918+
if (checkbox) {
919+
checkbox.checked = !enabled;
920+
}
921+
}
922+
}
923+
863924
// Theme management functions
864925
async applyTheme(theme) {
865926
const html = document.documentElement;

src/utils/theme-loader.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ class ThemeLoader {
3939
// that gets updated by the build process or manually maintained
4040

4141
// This could be enhanced to use a build-time script that generates this list
42-
const knownThemes = [
42+
const knownThemes = [
4343
'espresso.css',
4444
'pipboy.css',
4545
'pommodore64.css'

0 commit comments

Comments
 (0)