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
8 changes: 4 additions & 4 deletions packages/app/e2e/settings/settings-keybinds.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,21 +208,21 @@ test("changing new session keybind works", async ({ page, sdk, gotoSession }) =>
await keybindButton.click()
await expect(keybindButton).toHaveText(/press/i)

await page.keyboard.press(`${modKey}+Shift+KeyN`)
await page.keyboard.press(`${modKey}+Shift+KeyY`)
await page.waitForTimeout(100)

const newKeybind = await keybindButton.textContent()
expect(newKeybind).toContain("N")
expect(newKeybind).toContain("Y")

const stored = await page.evaluate(() => {
const raw = localStorage.getItem("settings.v3")
return raw ? JSON.parse(raw) : null
})
expect(stored?.keybinds?.["session.new"]).toBe("mod+shift+n")
expect(stored?.keybinds?.["session.new"]).toBe("mod+shift+y")

await closeDialog(page, dialog)

await page.keyboard.press(`${modKey}+Shift+N`)
await page.keyboard.press(`${modKey}+Shift+Y`)
await page.waitForTimeout(200)

const newUrl = page.url()
Expand Down
3 changes: 3 additions & 0 deletions packages/app/src/context/platform.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ export type Platform = {

/** Read image from clipboard (desktop only) */
readClipboardImage?(): Promise<File | null>

/** Spawn a new window (desktop only) */
spawnWindow?(): Promise<void>
}

export type DisplayBackend = "auto" | "wayland"
Expand Down
1 change: 1 addition & 0 deletions packages/app/src/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export const dict = {
"command.category.permissions": "Permissions",
"command.category.workspace": "Workspace",
"command.category.settings": "Settings",
"command.category.window": "Window",

"theme.scheme.system": "System",
"theme.scheme.light": "Light",
Expand Down
7 changes: 7 additions & 0 deletions packages/app/src/pages/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1150,6 +1150,13 @@ export default function Layout(props: ParentProps) {
keybind: "mod+shift+t",
onSelect: () => cycleTheme(1),
},
{
id: "window.new",
title: language.t("command.window.new"),
category: language.t("command.category.window"),
keybind: "mod+shift+n",
onSelect: () => platform.spawnWindow?.(),
},
]

for (const [id] of availableThemeEntries()) {
Expand Down
6 changes: 6 additions & 0 deletions packages/desktop-electron/src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,12 @@ registerIpcHandlers({
checkUpdate: async () => checkUpdate(),
installUpdate: async () => installUpdate(),
setBackgroundColor: (color) => setBackgroundColor(color),
spawnWindow: () => {
createMainWindow({
updaterEnabled: UPDATER_ENABLED,
deepLinks: [],
})
},
})

function killSidecar() {
Expand Down
2 changes: 2 additions & 0 deletions packages/desktop-electron/src/main/ipc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ type Deps = {
checkUpdate: () => Promise<{ updateAvailable: boolean; version?: string }>
installUpdate: () => Promise<void> | void
setBackgroundColor: (color: string) => void
spawnWindow: () => void
}

export function registerIpcHandlers(deps: Deps) {
Expand Down Expand Up @@ -60,6 +61,7 @@ export function registerIpcHandlers(deps: Deps) {
ipcMain.handle("check-update", () => deps.checkUpdate())
ipcMain.handle("install-update", () => deps.installUpdate())
ipcMain.handle("set-background-color", (_event: IpcMainInvokeEvent, color: string) => deps.setBackgroundColor(color))
ipcMain.handle("spawn-window", () => deps.spawnWindow())
ipcMain.handle("store-get", (_event: IpcMainInvokeEvent, name: string, key: string) => {
const store = getStore(name)
const value = store.get(key)
Expand Down
1 change: 1 addition & 0 deletions packages/desktop-electron/src/preload/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ const api: ElectronAPI = {
checkUpdate: () => ipcRenderer.invoke("check-update"),
installUpdate: () => ipcRenderer.invoke("install-update"),
setBackgroundColor: (color: string) => ipcRenderer.invoke("set-background-color", color),
spawnWindow: () => ipcRenderer.invoke("spawn-window"),
}

contextBridge.exposeInMainWorld("api", api)
1 change: 1 addition & 0 deletions packages/desktop-electron/src/preload/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,5 @@ export type ElectronAPI = {
checkUpdate: () => Promise<{ updateAvailable: boolean; version?: string }>
installUpdate: () => Promise<void>
setBackgroundColor: (color: string) => Promise<void>
spawnWindow: () => Promise<void>
}
4 changes: 4 additions & 0 deletions packages/desktop-electron/src/renderer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,10 @@ const createPlatform = (): Platform => {
type: "image/png",
})
},

spawnWindow: async () => {
await window.api.spawnWindow()
},
}
}

Expand Down
12 changes: 10 additions & 2 deletions packages/desktop/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,13 @@ fn resolve_app_path(app_name: &str) -> Option<String> {
}
}

#[tauri::command]
#[specta::specta]
fn spawn_window(app: AppHandle) -> Result<(), String> {
let label = uuid::Uuid::new_v4().to_string();
MainWindow::create(&app, &label).map(|_| ()).map_err(|e| e.to_string())
}

#[tauri::command]
#[specta::specta]
fn open_path(_app: AppHandle, path: String, app_name: Option<String>) -> Result<(), String> {
Expand Down Expand Up @@ -387,7 +394,8 @@ fn make_specta_builder() -> tauri_specta::Builder<tauri::Wry> {
check_app_exists,
wsl_path,
resolve_app_path,
open_path
open_path,
spawn_window
])
.events(tauri_specta::collect_events![
LoadingWindowComplete,
Expand Down Expand Up @@ -515,7 +523,7 @@ async fn initialize(app: AppHandle) {
};

// Create main window immediately - the web app handles its own loading/health gate
MainWindow::create(&app).expect("Failed to create main window");
MainWindow::create(&app, MainWindow::LABEL).expect("Failed to create main window");

let _ = loading_task.await;

Expand Down
6 changes: 3 additions & 3 deletions packages/desktop/src-tauri/src/windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ impl Deref for MainWindow {
impl MainWindow {
pub const LABEL: &str = "main";

pub fn create(app: &AppHandle) -> Result<Self, tauri::Error> {
if let Some(window) = app.get_webview_window(Self::LABEL) {
pub fn create(app: &AppHandle, label: &str) -> Result<Self, tauri::Error> {
if let Some(window) = app.get_webview_window(label) {
let _ = window.set_focus();
let _ = window.unminimize();
return Ok(Self(window));
Expand All @@ -49,7 +49,7 @@ impl MainWindow {
.unwrap_or(false);
let decorations = use_decorations();
let window_builder = base_window_config(
WebviewWindowBuilder::new(app, Self::LABEL, WebviewUrl::App("/".into())),
WebviewWindowBuilder::new(app, label, WebviewUrl::App("/".into())),
app,
decorations,
)
Expand Down
1 change: 1 addition & 0 deletions packages/desktop/src/bindings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export const commands = {
wslPath: (path: string, mode: "windows" | "linux" | null) => __TAURI_INVOKE<string>("wsl_path", { path, mode }),
resolveAppPath: (appName: string) => __TAURI_INVOKE<string | null>("resolve_app_path", { appName }),
openPath: (path: string, appName: string | null) => __TAURI_INVOKE<null>("open_path", { path, appName }),
spawnWindow: () => __TAURI_INVOKE<void>("spawn_window"),
};

/** Events */
Expand Down
3 changes: 3 additions & 0 deletions packages/desktop/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,9 @@ const createPlatform = (): Platform => {
}, "image/png")
})
},
spawnWindow: async () => {
await commands.spawnWindow()
},
}
}

Expand Down
Loading