Skip to content
Closed
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
27 changes: 27 additions & 0 deletions e2e/preferences.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,33 @@ test.describe('preferences', () => {
)
})

test('tray menu hosts list defaults on and can be switched off', async ({ page }) => {
await clearMockCalls(page)

await page.getByLabel('Settings').click()
await page.getByText('Preferences').click()
const preferences = page.getByRole('dialog')
await expect(preferences.getByText('General')).toBeVisible()
await preferences.getByRole('tab', { name: 'Advanced' }).click()

const hostsInTrayMenu = preferences.getByLabel('Switch Hosts from the Tray Menu')
await expect(hostsInTrayMenu).toBeChecked()

await hostsInTrayMenu.uncheck()

await expect
.poll(async () => {
const state = await getMockState(page)
return state.configs.show_hosts_in_tray_menu
})
.toBe(false)

const calls = await getMockCalls(page)
expect(configPatches(calls)).toEqual(
expect.arrayContaining([expect.objectContaining({ show_hosts_in_tray_menu: false })]),
)
})

test('automatic update checks default on and save immediately', async ({ page }) => {
await clearMockCalls(page)

Expand Down
1 change: 1 addition & 0 deletions e2e/support/tauri-mock.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
http_api_on: false,
http_api_only_local: true,
tray_mini_window: true,
show_hosts_in_tray_menu: true,
multi_chose_folder_switch_all: false,
auto_check_update: true,
find_is_regexp: false,
Expand Down
1 change: 1 addition & 0 deletions e2e/support/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ interface MockState {
proxy_port: number
refresh_remote_hosts_on_startup: boolean
auto_check_update: boolean
show_hosts_in_tray_menu: boolean
}
contents: Record<string, string>
systemHosts: string
Expand Down
44 changes: 37 additions & 7 deletions src-tauri/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,8 @@ fn apply_launch_at_login(app: &AppHandle<Wry>, enabled: bool) -> Result<(), Stor
/// - `http_api_on` / `http_api_only_local` → start, stop or rebind
/// the local HTTP API server.
/// - `locale` → rebuild native application and tray menus.
/// - `show_hosts_in_tray_menu` → rebuild the tray menu so its hosts
/// items appear or disappear.
/// - `show_title_on_tray` → refresh or clear the tray title text.
/// - `hide_dock_icon` → apply the macOS Dock policy and update the tray
/// toggle label.
Expand All @@ -295,15 +297,21 @@ fn apply_side_effects(app: &AppHandle<Wry>, state: &AppState, touched_keys: &[&s
let touches_locale = touched_keys.iter().any(|k| *k == "locale");
let touches_tray_title = touched_keys.iter().any(|k| *k == "show_title_on_tray");
let touches_auto_update = touched_keys.iter().any(|k| *k == "auto_check_update");
let touches_tray_hosts = touched_keys.iter().any(|k| *k == "show_hosts_in_tray_menu");

if touches_locale {
if let Err(e) = app_menu::refresh(app) {
log::warn!("failed to refresh app menu: {e}");
}
tray::refresh_menu(app);
find::refresh_find_window_title(app);
}

// The locale changes the menu's labels; the hosts preference changes
// whether it lists hosts entries at all.
if touches_locale || touches_tray_hosts {
tray::refresh_menu(app);
}

if touches_tray_title {
if let Err(e) = tray::refresh_title(app, state) {
log::warn!("failed to refresh tray title after config update: {e}");
Expand Down Expand Up @@ -661,6 +669,23 @@ pub async fn apply_hosts_selection<R: Runtime>(
}
};

Ok(apply_content_to_system(&app, state.inner(), &content).await)
}

/// Write `content` to the system hosts file and run every side effect
/// that has to follow a successful apply: the history journal, the
/// `system_hosts_updated` broadcast, the tray title, and
/// `cmd_after_hosts_apply`.
///
/// Shared by the renderer's `apply_hosts_selection` command and the
/// tray menu's native toggle (`hosts_toggle::toggle_item`), so both
/// paths journal and notify identically. Returns the same JSON envelope
/// the renderer has always received.
pub(crate) async fn apply_content_to_system<R: Runtime>(
app: &AppHandle<R>,
state: &AppState,
content: &str,
) -> Value {
let (write_mode, history_limit, cmd_after_apply) = {
let cfg = state.config.lock().expect("config mutex poisoned");
(
Expand All @@ -674,14 +699,14 @@ pub async fn apply_hosts_selection<R: Runtime>(
// user at the OS auth prompt) and *must not* hold the store_lock —
// see implementation-notes A5. We do all the work outside the lock
// and only retake it for the history journal write below.
let outcome = match hosts_apply::apply_to_system_hosts(&content, &write_mode) {
let outcome = match hosts_apply::apply_to_system_hosts(content, &write_mode) {
Ok(o) => o,
Err(HostsApplyError::Cancelled) => {
return Ok(HostsApplyError::Cancelled.into_renderer_value());
return HostsApplyError::Cancelled.into_renderer_value();
}
Err(e) => {
log::warn!("apply failed: {e}");
return Ok(e.into_renderer_value());
return e.into_renderer_value();
}
};

Expand Down Expand Up @@ -729,7 +754,7 @@ pub async fn apply_hosts_selection<R: Runtime>(
// Push the freshest tray title to the menubar without waiting on
// the renderer to call `update_tray_title` — the user expects to
// see the title flip immediately after an apply.
if let Err(e) = tray::refresh_title(&app, state.inner()) {
if let Err(e) = tray::refresh_title(app, state) {
log::warn!("failed to refresh tray title: {e}");
}

Expand All @@ -754,11 +779,11 @@ pub async fn apply_hosts_selection<R: Runtime>(
}
}

Ok(json!({
json!({
"success": true,
"old_content": outcome.previous_content,
"new_content": outcome.new_content,
}))
})
}

// ---- privileged helper (macOS SMAppService) --------------------------------
Expand Down Expand Up @@ -1155,6 +1180,11 @@ pub async fn update_tray_title<R: Runtime>(
_args: Args,
) -> Result<Value, StorageError> {
tray::refresh_title(&app, &state)?;
// The tray menu lists every hosts entry with its on/off mark, and
// the renderer calls this straight after persisting any list change
// (`useHostsData::setList`) — so this is also where the menu learns
// about renames, new entries, deletions and toggles.
tray::refresh_menu(&app);
Ok(Value::Null)
}

Expand Down
Loading