Add private window entry point

This commit is contained in:
2026-05-09 00:31:40 -04:00
parent 97a06d7b52
commit 7797203bbf
6 changed files with 139 additions and 29 deletions
+39 -8
View File
@@ -19,6 +19,7 @@ actions!(
OpenDownloads, OpenDownloads,
OpenHistory, OpenHistory,
OpenNewTab, OpenNewTab,
OpenPrivateWindow,
OpenSettings, OpenSettings,
OpenTaskManager, OpenTaskManager,
Quit, Quit,
@@ -39,6 +40,7 @@ fn main() {
gpui_component::init(cx); gpui_component::init(cx);
cx.on_action(quit); cx.on_action(quit);
bind_shortcuts(cx); bind_shortcuts(cx);
cx.on_action(open_private_window);
cx.set_menus(vec![ cx.set_menus(vec![
Menu { Menu {
name: "ELY Browser".into(), name: "ELY Browser".into(),
@@ -52,6 +54,7 @@ fn main() {
name: "File".into(), name: "File".into(),
items: vec![ items: vec![
MenuItem::action("New Tab", OpenNewTab), MenuItem::action("New Tab", OpenNewTab),
MenuItem::action("New Private Window", OpenPrivateWindow),
MenuItem::action("Split Right", SplitRight), MenuItem::action("Split Right", SplitRight),
MenuItem::separator(), MenuItem::separator(),
MenuItem::action("Command Mode", FocusCommandMode), MenuItem::action("Command Mode", FocusCommandMode),
@@ -78,11 +81,35 @@ fn main() {
}, },
]); ]);
if open_browser_window(cx, BrowserWindowMode::Standard) {
cx.activate(true);
} else {
cx.quit();
}
});
}
#[derive(Clone, Copy)]
enum BrowserWindowMode {
Standard,
Private,
}
impl BrowserWindowMode {
fn title(self) -> &'static str {
match self {
Self::Standard => "ELY Browser",
Self::Private => "ELY Browser - Private",
}
}
}
fn open_browser_window(cx: &mut App, mode: BrowserWindowMode) -> bool {
let bounds = Bounds::centered(None, size(px(1240.0), px(780.0)), cx); let bounds = Bounds::centered(None, size(px(1240.0), px(780.0)), cx);
let opened = cx.open_window( let opened = cx.open_window(
WindowOptions { WindowOptions {
titlebar: Some(TitlebarOptions { titlebar: Some(TitlebarOptions {
title: Some("ELY Browser".into()), title: Some(mode.title().into()),
appears_transparent: true, appears_transparent: true,
traffic_light_position: Some(point(px(18.0), px(24.0))), traffic_light_position: Some(point(px(18.0), px(24.0))),
}), }),
@@ -90,7 +117,10 @@ fn main() {
..WindowOptions::default() ..WindowOptions::default()
}, },
|window, cx| { |window, cx| {
let shell = cx.new(|cx| ElyShell::new(window, cx)); let shell = cx.new(|cx| match mode {
BrowserWindowMode::Standard => ElyShell::new(window, cx),
BrowserWindowMode::Private => ElyShell::new_private(window, cx),
});
let focus_handle = shell.focus_handle(cx); let focus_handle = shell.focus_handle(cx);
window.defer(cx, move |window, cx| { window.defer(cx, move |window, cx| {
if window.focused(cx).is_none() { if window.focused(cx).is_none() {
@@ -101,14 +131,15 @@ fn main() {
}, },
); );
if opened.is_ok() { opened.is_ok()
cx.activate(true);
} else {
cx.quit();
}
});
} }
fn quit(_: &Quit, cx: &mut App) { fn quit(_: &Quit, cx: &mut App) {
cx.quit(); cx.quit();
} }
fn open_private_window(_: &OpenPrivateWindow, cx: &mut App) {
if open_browser_window(cx, BrowserWindowMode::Private) {
cx.activate(true);
}
}
+13 -1
View File
@@ -77,6 +77,18 @@ pub struct ElyShell {
impl ElyShell { impl ElyShell {
pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self { pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
Self::new_with_config(InitialBrowserConfig::ely_defaults(), window, cx)
}
pub fn new_private(window: &mut Window, cx: &mut Context<Self>) -> Self {
Self::new_with_config(InitialBrowserConfig::private_window(), window, cx)
}
fn new_with_config(
config: Result<InitialBrowserConfig, ely_domain::DomainError>,
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
let command_input = let command_input =
cx.new(|cx| InputState::new(window, cx).placeholder("Search or enter address")); cx.new(|cx| InputState::new(window, cx).placeholder("Search or enter address"));
@@ -112,7 +124,7 @@ impl ElyShell {
}, },
); );
let state = match InitialBrowserConfig::ely_defaults().and_then(|config| { let state = match config.and_then(|config| {
BrowserCore::new(config).map_err(|error| match error { BrowserCore::new(config).map_err(|error| match error {
ely_browser_core::CoreError::Domain(source) => source, ely_browser_core::CoreError::Domain(source) => source,
_ => ely_domain::DomainError::InvalidCommand, _ => ely_domain::DomainError::InvalidCommand,
@@ -74,7 +74,10 @@ mod tests {
let mut core = BrowserCore::new(InitialBrowserConfig { let mut core = BrowserCore::new(InitialBrowserConfig {
space_name: "Work".to_string(), space_name: "Work".to_string(),
space_icon: "W".to_string(), space_icon: "W".to_string(),
space_color_hex: 0xf54e00,
profile_name: "Default".to_string(), profile_name: "Default".to_string(),
profile_color_hex: 0x26251e,
profile_kind: ely_domain::ProfileKind::Standard,
new_tab_destination: Default::default(), new_tab_destination: Default::default(),
})?; })?;
core.open_tab(UrlText::parse(url)?); core.open_tab(UrlText::parse(url)?);
+27 -2
View File
@@ -4,8 +4,9 @@ use gpui::{App, KeyBinding};
use crate::{ use crate::{
CloseCurrentTab, FocusAddressBar, FocusCommandMode, OpenDownloads, OpenHistory, OpenNewTab, CloseCurrentTab, FocusAddressBar, FocusCommandMode, OpenDownloads, OpenHistory, OpenNewTab,
OpenSettings, OpenTaskManager, Quit, RestoreClosedTab, SelectNextSpace, SelectNextTab, OpenPrivateWindow, OpenSettings, OpenTaskManager, Quit, RestoreClosedTab, SelectNextSpace,
SelectPreviousSpace, SelectPreviousTab, SplitRight, ToggleFavoriteTab, ToggleSidebar, SelectNextTab, SelectPreviousSpace, SelectPreviousTab, SplitRight, ToggleFavoriteTab,
ToggleSidebar,
}; };
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] #[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
@@ -28,6 +29,7 @@ pub(crate) enum ShortcutAction {
FocusAddressBar, FocusAddressBar,
FocusCommandMode, FocusCommandMode,
OpenNewTab, OpenNewTab,
OpenPrivateWindow,
CloseCurrentTab, CloseCurrentTab,
RestoreClosedTab, RestoreClosedTab,
SelectNextSpace, SelectNextSpace,
@@ -50,6 +52,7 @@ impl ShortcutAction {
Self::FocusAddressBar => "Command Bar", Self::FocusAddressBar => "Command Bar",
Self::FocusCommandMode => "Command Mode", Self::FocusCommandMode => "Command Mode",
Self::OpenNewTab => "New Tab", Self::OpenNewTab => "New Tab",
Self::OpenPrivateWindow => "New Private Window",
Self::CloseCurrentTab => "Close Tab", Self::CloseCurrentTab => "Close Tab",
Self::RestoreClosedTab => "Restore Closed Tab", Self::RestoreClosedTab => "Restore Closed Tab",
Self::SelectNextSpace => "Next Space", Self::SelectNextSpace => "Next Space",
@@ -71,6 +74,7 @@ impl ShortcutAction {
match self { match self {
Self::FocusAddressBar | Self::FocusCommandMode => "Command", Self::FocusAddressBar | Self::FocusCommandMode => "Command",
Self::OpenNewTab Self::OpenNewTab
| Self::OpenPrivateWindow
| Self::CloseCurrentTab | Self::CloseCurrentTab
| Self::RestoreClosedTab | Self::RestoreClosedTab
| Self::SelectNextSpace | Self::SelectNextSpace
@@ -91,6 +95,7 @@ impl ShortcutAction {
Self::FocusAddressBar => None, Self::FocusAddressBar => None,
Self::FocusCommandMode => None, Self::FocusCommandMode => None,
Self::OpenNewTab => Some(">new-tab"), Self::OpenNewTab => Some(">new-tab"),
Self::OpenPrivateWindow => None,
Self::CloseCurrentTab => Some(">close-tab"), Self::CloseCurrentTab => Some(">close-tab"),
Self::RestoreClosedTab => Some(">restore-tab"), Self::RestoreClosedTab => Some(">restore-tab"),
Self::SelectNextSpace => None, Self::SelectNextSpace => None,
@@ -133,6 +138,7 @@ pub(crate) const SHORTCUT_ACTIONS: &[ShortcutAction] = &[
ShortcutAction::FocusAddressBar, ShortcutAction::FocusAddressBar,
ShortcutAction::FocusCommandMode, ShortcutAction::FocusCommandMode,
ShortcutAction::OpenNewTab, ShortcutAction::OpenNewTab,
ShortcutAction::OpenPrivateWindow,
ShortcutAction::CloseCurrentTab, ShortcutAction::CloseCurrentTab,
ShortcutAction::RestoreClosedTab, ShortcutAction::RestoreClosedTab,
ShortcutAction::SelectNextSpace, ShortcutAction::SelectNextSpace,
@@ -152,6 +158,8 @@ pub(crate) const SHORTCUT_ACTIONS: &[ShortcutAction] = &[
pub(crate) const SHORTCUT_BINDINGS: &[ShortcutBinding] = &[ pub(crate) const SHORTCUT_BINDINGS: &[ShortcutBinding] = &[
shortcut(ShortcutAction::OpenNewTab, ShortcutPlatform::Macos, "cmd-t"), shortcut(ShortcutAction::OpenNewTab, ShortcutPlatform::Macos, "cmd-t"),
shortcut(ShortcutAction::OpenNewTab, ShortcutPlatform::WindowsLinux, "ctrl-t"), shortcut(ShortcutAction::OpenNewTab, ShortcutPlatform::WindowsLinux, "ctrl-t"),
shortcut(ShortcutAction::OpenPrivateWindow, ShortcutPlatform::Macos, "cmd-shift-n"),
shortcut(ShortcutAction::OpenPrivateWindow, ShortcutPlatform::WindowsLinux, "ctrl-shift-n"),
shortcut(ShortcutAction::SplitRight, ShortcutPlatform::Macos, "cmd-\\"), shortcut(ShortcutAction::SplitRight, ShortcutPlatform::Macos, "cmd-\\"),
shortcut(ShortcutAction::SplitRight, ShortcutPlatform::WindowsLinux, "ctrl-\\"), shortcut(ShortcutAction::SplitRight, ShortcutPlatform::WindowsLinux, "ctrl-\\"),
shortcut(ShortcutAction::ToggleSidebar, ShortcutPlatform::Macos, "cmd-b"), shortcut(ShortcutAction::ToggleSidebar, ShortcutPlatform::Macos, "cmd-b"),
@@ -245,6 +253,9 @@ impl ShortcutBinding {
ShortcutAction::OpenDownloads => KeyBinding::new(self.keystroke, OpenDownloads, None), ShortcutAction::OpenDownloads => KeyBinding::new(self.keystroke, OpenDownloads, None),
ShortcutAction::OpenHistory => KeyBinding::new(self.keystroke, OpenHistory, None), ShortcutAction::OpenHistory => KeyBinding::new(self.keystroke, OpenHistory, None),
ShortcutAction::OpenNewTab => KeyBinding::new(self.keystroke, OpenNewTab, None), ShortcutAction::OpenNewTab => KeyBinding::new(self.keystroke, OpenNewTab, None),
ShortcutAction::OpenPrivateWindow => {
KeyBinding::new(self.keystroke, OpenPrivateWindow, None)
}
ShortcutAction::OpenSettings => KeyBinding::new(self.keystroke, OpenSettings, None), ShortcutAction::OpenSettings => KeyBinding::new(self.keystroke, OpenSettings, None),
ShortcutAction::OpenTaskManager => { ShortcutAction::OpenTaskManager => {
KeyBinding::new(self.keystroke, OpenTaskManager, None) KeyBinding::new(self.keystroke, OpenTaskManager, None)
@@ -317,6 +328,20 @@ mod tests {
assert_eq!(bindings, vec!["Cmd + ,".to_string(), "Ctrl + ,".to_string()]); assert_eq!(bindings, vec!["Cmd + ,".to_string(), "Ctrl + ,".to_string()]);
} }
#[test]
fn private_window_shortcut_has_platform_bindings() {
let bindings =
bindings_for_action(ShortcutAction::OpenPrivateWindow, ShortcutPlatform::Macos)
.chain(bindings_for_action(
ShortcutAction::OpenPrivateWindow,
ShortcutPlatform::WindowsLinux,
))
.map(|binding| binding.display_keystroke())
.collect::<Vec<_>>();
assert_eq!(bindings, vec!["Cmd + Shift + N".to_string(), "Ctrl + Shift + N".to_string()]);
}
#[test] #[test]
fn toggle_sidebar_shortcut_has_platform_bindings() { fn toggle_sidebar_shortcut_has_platform_bindings() {
let bindings = bindings_for_action(ShortcutAction::ToggleSidebar, ShortcutPlatform::Macos) let bindings = bindings_for_action(ShortcutAction::ToggleSidebar, ShortcutPlatform::Macos)
+21 -2
View File
@@ -43,7 +43,10 @@ pub use spaces::TrashedSpace;
pub struct InitialBrowserConfig { pub struct InitialBrowserConfig {
pub space_name: String, pub space_name: String,
pub space_icon: String, pub space_icon: String,
pub space_color_hex: u32,
pub profile_name: String, pub profile_name: String,
pub profile_color_hex: u32,
pub profile_kind: ProfileKind,
pub new_tab_destination: NewTabDestination, pub new_tab_destination: NewTabDestination,
} }
@@ -52,7 +55,22 @@ impl InitialBrowserConfig {
Ok(Self { Ok(Self {
space_name: "Work".to_string(), space_name: "Work".to_string(),
space_icon: "W".to_string(), space_icon: "W".to_string(),
space_color_hex: 0xf54e00,
profile_name: "Default".to_string(), profile_name: "Default".to_string(),
profile_color_hex: 0x26251e,
profile_kind: ProfileKind::Standard,
new_tab_destination: NewTabDestination::default(),
})
}
pub fn private_window() -> Result<Self, DomainError> {
Ok(Self {
space_name: "Private".to_string(),
space_icon: "P".to_string(),
space_color_hex: 0x807d72,
profile_name: "Private".to_string(),
profile_color_hex: 0x807d72,
profile_kind: ProfileKind::Private,
new_tab_destination: NewTabDestination::default(), new_tab_destination: NewTabDestination::default(),
}) })
} }
@@ -127,12 +145,13 @@ pub struct BrowserCore {
impl BrowserCore { impl BrowserCore {
pub fn new(config: InitialBrowserConfig) -> Result<Self, CoreError> { pub fn new(config: InitialBrowserConfig) -> Result<Self, CoreError> {
let profile = Profile::new(config.profile_name, 0x26251e, ProfileKind::Standard); let profile =
Profile::new(config.profile_name, config.profile_color_hex, config.profile_kind);
let active_profile_id = profile.id().clone(); let active_profile_id = profile.id().clone();
let space = Space::new( let space = Space::new(
config.space_name, config.space_name,
config.space_icon, config.space_icon,
0xf54e00, config.space_color_hex,
active_profile_id.clone(), active_profile_id.clone(),
0, 0,
); );
+20
View File
@@ -24,6 +24,26 @@ fn new_private_profile_command_creates_private_profile() -> Result<(), Box<dyn E
Ok(()) Ok(())
} }
#[test]
fn private_window_config_starts_with_private_profile() -> Result<(), Box<dyn Error>> {
let mut core = BrowserCore::new(InitialBrowserConfig::private_window()?)?;
let snapshot = core.snapshot()?;
let Some(private_profile) =
snapshot.profiles.iter().find(|profile| profile.id() == &snapshot.active_profile_id)
else {
return Err("missing active private profile".into());
};
assert_eq!(snapshot.active_space_name, "Private");
assert_eq!(snapshot.active_profile_name, "Private");
assert_eq!(private_profile.kind(), &ProfileKind::Private);
assert_eq!(private_profile.sync_policy(), ProfileSyncPolicy::Paused);
core.open_tab(UrlText::parse("https://example.com/private-window")?);
assert!(core.snapshot()?.history_entries.is_empty());
Ok(())
}
#[test] #[test]
fn private_profiles_do_not_record_history() -> Result<(), Box<dyn Error>> { fn private_profiles_do_not_record_history() -> Result<(), Box<dyn Error>> {
let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?; let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;