Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,6 @@ base64 = "0.21"
core-graphics = "0.23"
core-foundation = "0.9"
libc = "0.2"
cocoa = "0.25"
objc = "0.2"

92 changes: 91 additions & 1 deletion src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ struct AppSettings {
autostart: bool,
#[serde(default = "default_analytics_enabled")]
analytics_enabled: bool,
#[serde(default)]
hide_icon_on_close: bool,
}

#[derive(Serialize, Deserialize, Clone)]
Expand Down Expand Up @@ -174,6 +176,7 @@ impl Default for AppSettings {
advanced: AdvancedSettings::default(),
autostart: false, // default to disabled
analytics_enabled: true, // default to enabled
hide_icon_on_close: false, // default to disabled
}
}
}
Expand Down Expand Up @@ -612,6 +615,22 @@ async fn update_tray_icon(
#[tauri::command]
async fn show_window(app: AppHandle) -> Result<(), String> {
if let Some(window) = app.get_webview_window("main") {
// Check if hide_icon_on_close is enabled to restore dock visibility
match load_settings(app.clone()).await {
Ok(settings) => {
if settings.hide_icon_on_close {
// Restore dock visibility when showing window
#[cfg(target_os = "macos")]
{
let _ = set_dock_visibility(app.clone(), true).await;
}
}
}
Err(_) => {
// Ignore error, just proceed with showing window
}
}

window
.show()
.map_err(|e| format!("Failed to show window: {}", e))?;
Expand Down Expand Up @@ -929,7 +948,8 @@ pub fn run() {
save_session_tags,
add_session_tag,
write_excel_file,
start_oauth_server
start_oauth_server,
set_dock_visibility
])
.setup(|app| {
// Track app started event (if enabled)
Expand Down Expand Up @@ -1023,9 +1043,42 @@ pub fn run() {
.build(app)?;

if let Some(window) = app.get_webview_window("main") {
let app_handle_for_close = app.handle().clone();
window.on_window_event(move |event| {
if let tauri::WindowEvent::CloseRequested { api, .. } = event {
// Always prevent close
api.prevent_close();

// Check if we should hide the app icon
let app_handle_clone = app_handle_for_close.clone();
tauri::async_runtime::spawn(async move {
match load_settings(app_handle_clone.clone()).await {
Ok(settings) => {
if settings.hide_icon_on_close {
// Hide the window and set app as dock hidden
if let Some(window) = app_handle_clone.get_webview_window("main") {
let _ = window.hide();
// Use macOS specific API to hide from dock
#[cfg(target_os = "macos")]
{
let _ = set_dock_visibility(app_handle_clone.clone(), false).await;
}
}
} else {
// Just hide the window without hiding from dock
if let Some(window) = app_handle_clone.get_webview_window("main") {
let _ = window.hide();
}
}
}
Err(_) => {
// Default behavior: just hide the window
if let Some(window) = app_handle_clone.get_webview_window("main") {
let _ = window.hide();
}
}
}
});
}
});
}
Expand Down Expand Up @@ -1283,3 +1336,40 @@ async fn start_oauth_server(window: tauri::Window) -> Result<u16, String> {
})
.map_err(|err| err.to_string())
}

#[tauri::command]
async fn set_dock_visibility(app: AppHandle, visible: bool) -> Result<(), String> {
#[cfg(target_os = "macos")]
{
app.run_on_main_thread(move || {
set_dock_visibility_native(visible);
})
.map_err(|e| format!("Failed to run on main thread: {}", e))?;
}

#[cfg(not(target_os = "macos"))]
{
return Err("Dock visibility is only supported on macOS".to_string());
}

Ok(())
}

#[cfg(target_os = "macos")]
fn set_dock_visibility_native(visible: bool) {
use cocoa::appkit::{NSApp, NSApplication, NSApplicationActivationPolicy};
use cocoa::base::nil;

unsafe {
let app = NSApp();
if app != nil {
let policy = if visible {
NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular
} else {
NSApplicationActivationPolicy::NSApplicationActivationPolicyAccessory
};

app.setActivationPolicy_(policy);
}
}
}
9 changes: 9 additions & 0 deletions src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -797,6 +797,15 @@ <h3>System Integration</h3>
start
minimized to the system tray.</p>
</div>

<div class="setting-item">
<label class="checkbox-label">
<input type="checkbox" id="hide-icon-on-close">
<span class="checkmark"></span>
Hide Icon on Close
</label>
<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>
</div>
</div>

<div class="settings-section">
Expand Down
65 changes: 63 additions & 2 deletions src/managers/settings-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ export class SettingsManager {
appearance: { ...defaultSettings.appearance, ...loadedSettings.appearance },
advanced: { ...defaultSettings.advanced, ...loadedSettings.advanced },
autostart: loadedSettings.autostart !== undefined ? loadedSettings.autostart : defaultSettings.autostart,
analytics_enabled: loadedSettings.analytics_enabled !== undefined ? loadedSettings.analytics_enabled : defaultSettings.analytics_enabled
analytics_enabled: loadedSettings.analytics_enabled !== undefined ? loadedSettings.analytics_enabled : defaultSettings.analytics_enabled,
hide_icon_on_close: loadedSettings.hide_icon_on_close !== undefined ? loadedSettings.hide_icon_on_close : defaultSettings.hide_icon_on_close
};
}

Expand Down Expand Up @@ -118,7 +119,8 @@ export class SettingsManager {
debug_mode: false // Debug mode with 3-second timers
},
autostart: false, // default to disabled
analytics_enabled: true // Analytics enabled by default
analytics_enabled: true, // Analytics enabled by default
hide_icon_on_close: false // Hide icon on close disabled by default
};
}

Expand Down Expand Up @@ -198,6 +200,9 @@ export class SettingsManager {

// Populate analytics setting
this.loadAnalyticsSetting();

// Populate hide icon on close setting
this.loadHideIconOnCloseSetting();
}

setupEventListeners() {
Expand Down Expand Up @@ -860,6 +865,62 @@ export class SettingsManager {
}
}

async loadHideIconOnCloseSetting() {
try {
// Get current hide icon on close setting from our stored settings
const hideIconOnClose = this.settings.hide_icon_on_close;

const checkbox = document.getElementById('hide-icon-on-close');
if (checkbox) {
checkbox.checked = hideIconOnClose;

// Setup event listener for the hide icon on close checkbox
checkbox.addEventListener('change', async (e) => {
await this.toggleHideIconOnClose(e.target.checked);
});
}
} catch (error) {
console.error('Failed to load hide icon on close setting:', error);
// Default to disabled if we can't check the status
const checkbox = document.getElementById('hide-icon-on-close');
if (checkbox) {
checkbox.checked = false;
checkbox.addEventListener('change', async (e) => {
await this.toggleHideIconOnClose(e.target.checked);
});
}
}
}

async toggleHideIconOnClose(enabled) {
try {
// Update our settings
this.settings.hide_icon_on_close = enabled;

// Show user feedback
if (enabled) {
console.log('Hide icon on close enabled');
NotificationUtils.showNotificationPing('✓ Hide icon on close enabled - App will hide from dock when closed', 'success');
} else {
console.log('Hide icon on close disabled');
NotificationUtils.showNotificationPing('✓ Hide icon on close disabled - App will remain visible in dock', 'success');
}

// Schedule auto-save to persist the setting
this.scheduleAutoSave();

} catch (error) {
console.error('Failed to toggle hide icon on close:', error);
NotificationUtils.showNotificationPing('❌ Failed to toggle hide icon on close: ' + error, 'error');

// Revert the checkbox state on error
const checkbox = document.getElementById('hide-icon-on-close');
if (checkbox) {
checkbox.checked = !enabled;
}
}
}

// Theme management functions
async applyTheme(theme) {
const html = document.documentElement;
Expand Down
2 changes: 1 addition & 1 deletion src/utils/theme-loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class ThemeLoader {
// that gets updated by the build process or manually maintained

// This could be enhanced to use a build-time script that generates this list
const knownThemes = [
const knownThemes = [
'espresso.css',
'pipboy.css',
'pommodore64.css'
Expand Down