Skip to content
Open
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
1 change: 1 addition & 0 deletions app/src/root_view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -760,6 +760,7 @@ fn open_from_restored(arg: &OpenFromRestoredArg, ctx: &mut AppContext) {

if let Some(app_state) = &arg.app_state {
maybe_register_global_window_shortcuts(global_resource_handles.clone(), ctx);
KeysSettings::as_ref(ctx).apply_effective_dock_icon_visibility(ctx);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [IMPORTANT] This is still inside if let Some(app_state), so launches without a persisted app state skip applying the saved Dock visibility setting; move the apply outside the if or run it from the launch path after settings load so startup always enforces the setting.


let (background_blur_radius_pixels, background_blur_texture) = {
let window_settings = WindowSettings::as_ref(ctx);
Expand Down
5 changes: 5 additions & 0 deletions app/src/settings/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,10 @@ pub struct QuakeModeSettings {
/// user focuses on another warp window or another app.
#[schemars(description = "Whether to hide the hotkey window when it loses focus.")]
pub hide_window_when_unfocused: bool,
#[schemars(
description = "macOS only. Whether Warp should hide its Dock icon while the dedicated hotkey window is the active global hotkey mode and a keybinding is configured. Has no effect on Linux or Windows."
)]
pub hide_dock_icon: bool,
Comment on lines +343 to +346
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [IMPORTANT] Existing user configs serialized before this field existed will not contain hide_dock_icon; without a serde default, deserializing QuakeModeSettings can fail instead of falling back to false.

Suggested change
#[schemars(
description = "macOS only. Whether Warp should hide its Dock icon while the dedicated hotkey window is the active global hotkey mode and a keybinding is configured. Has no effect on Linux or Windows."
)]
pub hide_dock_icon: bool,
#[serde(default)]
#[schemars(
description = "macOS only. Whether Warp should hide its Dock icon while the dedicated hotkey window is the active global hotkey mode and a keybinding is configured. Has no effect on Linux or Windows."
)]
pub hide_dock_icon: bool,

}

impl Default for QuakeModeSettings {
Expand All @@ -351,6 +355,7 @@ impl Default for QuakeModeSettings {
pin_screen: Default::default(),
// Defaults to `true` only when it's supported on this platform.
hide_window_when_unfocused: QUAKE_WINDOW_AUTOHIDE_SUPPORTED,
hide_dock_icon: false,
}
}
}
Expand Down
62 changes: 62 additions & 0 deletions app/src/settings_view/features_page.rs
Original file line number Diff line number Diff line change
Expand Up @@ -598,6 +598,7 @@ pub enum FeaturesPageAction {
QuakeEditorSetHeightPercentage,
QuakeEditorResetWidthHeight,
QuakeEditorTogglePinWindow,
QuakeEditorToggleHideDockIcon,
OpenUrl(String),
SetExtraMetaKeys(ExtraMetaKeys),
ToggleLeftMetaKey,
Expand Down Expand Up @@ -913,6 +914,10 @@ impl FeaturesPageAction {
.hide_window_when_unfocused,
),
},
Self::QuakeEditorToggleHideDockIcon => TelemetryEvent::FeaturesPageAction {
action: "QuakeEditorToggleHideDockIcon".to_string(),
value: to_string(KeysSettings::as_ref(ctx).quake_mode_settings.hide_dock_icon),
},
Self::ToggleLongRunningNotifications => TelemetryEvent::FeaturesPageAction {
action: "ToggleLongRunningNotifications".to_string(),
value: to_string(
Expand Down Expand Up @@ -1168,6 +1173,8 @@ struct MouseStateHandles {
quake_mode_cancel: MouseStateHandle,
quake_mode_width_height_reset: MouseStateHandle,
quake_mode_pin_window_check: MouseStateHandle,
#[cfg(target_os = "macos")]
quake_mode_hide_dock_icon_check: MouseStateHandle,
long_running_notifications_checkbox: MouseStateHandle,
agent_task_completed_notifications_checkbox: MouseStateHandle,
agent_needs_attention_notifications_checkbox: MouseStateHandle,
Expand Down Expand Up @@ -1457,6 +1464,12 @@ impl TypedActionView for FeaturesPageView {
)
});
}
QuakeEditorToggleHideDockIcon => {
KeysSettings::handle(ctx).update(ctx, |keys_settings, ctx| {
keys_settings
.toggle_hide_dock_icon_when_using_quake_mode_and_write_to_user_defaults(ctx)
});
}
SetExtraMetaKeys(extra_meta_keys) => {
KeysSettings::handle(ctx).update(ctx, |keys_settings, ctx| {
report_if_error!(keys_settings
Expand Down Expand Up @@ -3603,6 +3616,47 @@ impl FeaturesPageView {
.finish()
}

#[cfg(target_os = "macos")]
fn render_quake_mode_hide_dock_icon_row(
&self,
quake_mode_settings: &QuakeModeSettings,
appearance: &Appearance,
) -> Box<dyn Element> {
Container::new(
Flex::row()
.with_child(
appearance
.ui_builder()
.checkbox(
self.button_mouse_states
.quake_mode_hide_dock_icon_check
.clone(),
None,
)
.check(quake_mode_settings.hide_dock_icon)
.build()
.on_click(move |ctx, _, _| {
ctx.dispatch_typed_action(
FeaturesPageAction::QuakeEditorToggleHideDockIcon,
)
})
.finish(),
)
.with_child(
appearance
.ui_builder()
.span("Hide Warp from the Dock while a dedicated hotkey is configured (also removes Warp from Cmd-Tab)")
.build()
.with_margin_left(5.)
.finish(),
)
.with_cross_axis_alignment(CrossAxisAlignment::Center)
.finish(),
)
.with_margin_bottom(2.)
.finish()
}

fn render_quake_mode_position_row(
&self,
quake_mode_settings: &QuakeModeSettings,
Expand Down Expand Up @@ -5447,6 +5501,14 @@ impl SettingsWidget for GlobalHotkeyWidget {
} else {
Empty::new().finish()
},
// Hiding the Dock icon depends on macOS NSApplicationActivationPolicy.
#[cfg(target_os = "macos")]
view.render_quake_mode_hide_dock_icon_row(
KeysSettings::as_ref(app).quake_mode_settings.value(),
appearance,
),
#[cfg(not(target_os = "macos"))]
Empty::new().finish(),
],
appearance,
));
Expand Down
134 changes: 134 additions & 0 deletions app/src/terminal/keys_settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ impl KeysSettings {
report_if_error!(self
.activation_hotkey_enabled
.set_value(enable_activation_hotkey, ctx));

self.apply_effective_dock_icon_visibility(ctx);
}

// Note that registering an empty keybinding when enabling quake mode will be a no-op.
Expand All @@ -101,6 +103,8 @@ impl KeysSettings {
quake_mode_settings.keybinding = keystroke;

report_if_error!(self.quake_mode_settings.set_value(quake_mode_settings, ctx));

self.apply_effective_dock_icon_visibility(ctx);
}

pub fn set_activation_hotkey_keybinding_and_write_to_user_defaults(
Expand Down Expand Up @@ -189,6 +193,54 @@ impl KeysSettings {
report_if_error!(self.quake_mode_settings.set_value(quake_mode_settings, ctx));
}

pub fn set_hide_dock_icon_when_using_quake_mode_and_write_to_user_defaults(
&mut self,
value: bool,
ctx: &mut ModelContext<Self>,
) {
let mut quake_mode_settings = self.quake_mode_settings.value().clone();
quake_mode_settings.hide_dock_icon = value;

report_if_error!(self.quake_mode_settings.set_value(quake_mode_settings, ctx));

self.apply_effective_dock_icon_visibility(ctx);
}

pub fn toggle_hide_dock_icon_when_using_quake_mode_and_write_to_user_defaults(
&mut self,
ctx: &mut ModelContext<Self>,
) {
let mut quake_mode_settings = self.quake_mode_settings.value().clone();
quake_mode_settings.hide_dock_icon = !quake_mode_settings.hide_dock_icon;

report_if_error!(self.quake_mode_settings.set_value(quake_mode_settings, ctx));

self.apply_effective_dock_icon_visibility(ctx);
}

/// Returns true when Warp should hide the Dock icon based on the current
/// effective hotkey configuration. Only true on macOS, when Quake Mode is
/// the global hotkey mode, the user enabled Dock hiding, and a dedicated
/// hotkey window keybinding is configured. The "hide" framing means
/// non-macOS and unsupported paths naturally default to visible.
pub fn should_hide_dock_icon(&self, app: &AppContext) -> bool {
let quake_mode_settings = self.quake_mode_settings.value();
compute_should_hide_dock_icon(
cfg!(target_os = "macos"),
self.global_hotkey_mode(app),
quake_mode_settings.hide_dock_icon,
quake_mode_settings.keybinding.is_some(),
)
}

/// Computes the effective Dock visibility state and asks the platform to
/// apply it. Safe to call from settings change paths and at startup. No-op
/// on non-macOS via the platform delegate's default implementation.
pub fn apply_effective_dock_icon_visibility(&self, app: &AppContext) {
let visible = !self.should_hide_dock_icon(app);
app.set_dock_icon_visible(visible);
}

pub fn global_hotkey_mode(&self, app: &AppContext) -> GlobalHotkeyMode {
let mut selected = GlobalHotkeyMode::Disabled;

Expand All @@ -209,3 +261,85 @@ impl KeysSettings {
selected
}
}

/// Pure decision function for whether Warp should hide its Dock icon, given
/// the effective hotkey configuration. Extracted so it can be unit-tested
/// without an `AppContext`. Returns true only when all four conditions hold:
/// running on macOS, Quake Mode is selected, the user opted in, and a
/// dedicated hotkey window keybinding is configured.
pub(crate) fn compute_should_hide_dock_icon(
is_macos: bool,
mode: GlobalHotkeyMode,
hide_dock_icon_setting: bool,
has_keybinding: bool,
) -> bool {
is_macos
&& matches!(mode, GlobalHotkeyMode::QuakeMode)
&& hide_dock_icon_setting
&& has_keybinding
}

#[cfg(test)]
mod tests {
use super::{compute_should_hide_dock_icon, GlobalHotkeyMode};

#[test]
fn hides_dock_icon_when_all_conditions_met() {
assert!(compute_should_hide_dock_icon(
true,
GlobalHotkeyMode::QuakeMode,
true,
true,
));
}

#[test]
fn default_setting_never_hides() {
assert!(!compute_should_hide_dock_icon(
true,
GlobalHotkeyMode::QuakeMode,
false,
true,
));
}

#[test]
fn activation_hotkey_mode_does_not_hide() {
assert!(!compute_should_hide_dock_icon(
true,
GlobalHotkeyMode::ActivationHotkey,
true,
true,
));
}

#[test]
fn disabled_mode_does_not_hide() {
assert!(!compute_should_hide_dock_icon(
true,
GlobalHotkeyMode::Disabled,
true,
true,
));
}

#[test]
fn missing_keybinding_does_not_hide() {
assert!(!compute_should_hide_dock_icon(
true,
GlobalHotkeyMode::QuakeMode,
true,
false,
));
}

#[test]
fn non_macos_never_hides_even_when_all_other_conditions_met() {
assert!(!compute_should_hide_dock_icon(
false,
GlobalHotkeyMode::QuakeMode,
true,
true,
));
}
}
8 changes: 8 additions & 0 deletions crates/warpui/src/platform/mac/delegate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,14 @@ impl platform::Delegate for AppDelegate {
}
}

fn set_dock_icon_visible(&self, visible: bool) {
// NSApplicationActivationPolicyRegular = 0, NSApplicationActivationPolicyAccessory = 1.
let policy: i64 = if visible { 0 } else { 1 };
dispatch::Queue::main().exec_async(move || unsafe {
let _: BOOL = msg_send![NSApp(), setActivationPolicy: policy];
});
}

fn terminate_app(&self, termination_mode: TerminationMode) {
// Execute `[NSApp terminate]` asynchronously on the main thread to
// ensure we don't accidentally run into any double-borrow errors.
Expand Down
5 changes: 5 additions & 0 deletions crates/warpui_core/src/core/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1126,6 +1126,11 @@ impl AppContext {
self.platform_delegate.register_global_shortcut(shortcut);
}

/// Show or hide the Dock icon (macOS only — no-op elsewhere).
pub fn set_dock_icon_visible(&self, visible: bool) {
self.platform_delegate.set_dock_icon_visible(visible);
}

fn dispatch_draw_frame_error_callback(&mut self, window_id: WindowId) {
let callback = self.on_draw_frame_error_callback.take();
if let Some(callback) = &callback {
Expand Down
4 changes: 4 additions & 0 deletions crates/warpui_core/src/platform/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,10 @@ pub trait Delegate: 'static {
fn register_global_shortcut(&self, shortcut: Keystroke);
fn unregister_global_shortcut(&self, shortcut: &Keystroke);

/// Show or hide the application's Dock icon (macOS only).
/// Default no-op for platforms without a Dock concept.
fn set_dock_icon_visible(&self, _visible: bool) {}

fn terminate_app(&self, termination_mode: TerminationMode);

/// Returns whether or not a screen reader is enabled, or None if we do not
Expand Down