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
1 change: 1 addition & 0 deletions src-tauri/capabilities/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"core:window:allow-close",
"core:window:allow-minimize",
"core:window:allow-start-dragging",
"core:window:allow-start-resize-dragging",
"core:window:allow-toggle-maximize",
"opener:default",
"dialog:default",
Expand Down
44 changes: 31 additions & 13 deletions src-tauri/src/app_menu.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
//! Native application menu management.

use tauri::{
menu::{AboutMetadata, CheckMenuItem, Menu, MenuItem, PredefinedMenuItem, Submenu},
AppHandle, Emitter, Runtime,
};
#[cfg(target_os = "macos")]
use tauri::menu::{AboutMetadata, CheckMenuItem, Menu, MenuItem, PredefinedMenuItem, Submenu};
use tauri::{AppHandle, Emitter, Runtime};

#[cfg(target_os = "macos")]
use crate::types::AppSettings;
#[cfg(target_os = "macos")]
pub(crate) use crate::types::DockDisplayMode;
use crate::{
auth::{load_app_settings, save_app_settings},
types::{AppSettings, TrayDisplayMode},
types::TrayDisplayMode,
};

#[cfg(target_os = "macos")]
const TRAY_ICON_AND_SESSION_ID: &str = "tray-display-icon-and-session";
#[cfg(target_os = "macos")]
const TRAY_ACTIVE_USAGE_TEXT_ID: &str = "tray-display-active-usage-text";
#[cfg(target_os = "macos")]
const TRAY_HIDDEN_ID: &str = "tray-display-hidden";
#[cfg(target_os = "macos")]
const DESKTOP_REOPEN_SETTINGS_ID: &str = "desktop-reopen-settings";
#[cfg(target_os = "macos")]
pub(crate) const DOCK_SHOW_IN_DOCK_ID: &str = "dock-display-show-in-dock";
Expand All @@ -23,22 +26,34 @@ pub(crate) const DOCK_MENU_BAR_ONLY_ID: &str = "dock-display-menu-bar-only";

pub fn setup(app: &AppHandle) -> tauri::Result<()> {
#[cfg(target_os = "macos")]
apply_saved_dock_display_mode(app);
refresh(app)?;
app.on_menu_event(handle_menu_event);
{
apply_saved_dock_display_mode(app);
refresh(app)?;
app.on_menu_event(handle_menu_event);
}
#[cfg(not(target_os = "macos"))]
{
// On non-macOS platforms, Codex Switcher uses a frameless custom UI (decorations: false).
// Settings are managed in the tray menu and in-app UI, so no native window menu is attached.
refresh(app)?;
}
Ok(())
}

pub fn refresh<R: Runtime>(app: &AppHandle<R>) -> tauri::Result<()> {
let settings = load_app_settings().unwrap_or_default();
let menu = build_menu(app, &settings)?;
app.set_menu(menu)?;
#[cfg(target_os = "macos")]
{
let settings = load_app_settings().unwrap_or_default();
let menu = build_menu(app, &settings)?;
app.set_menu(menu)?;
}
if let Err(error) = app.emit("app-settings-changed", ()) {
eprintln!("Failed to notify settings changes: {error}");
}
Ok(())
}

#[cfg(target_os = "macos")]
fn handle_menu_event(app: &AppHandle, event: tauri::menu::MenuEvent) {
let item_id = event.id();

Expand All @@ -61,6 +76,7 @@ fn handle_menu_event(app: &AppHandle, event: tauri::menu::MenuEvent) {
}
}

#[cfg(target_os = "macos")]
fn tray_display_mode_for_item(item_id: &str) -> Option<TrayDisplayMode> {
Some(match item_id {
TRAY_ICON_AND_SESSION_ID => TrayDisplayMode::IconAndSession,
Expand All @@ -70,6 +86,7 @@ fn tray_display_mode_for_item(item_id: &str) -> Option<TrayDisplayMode> {
})
}

#[cfg(target_os = "macos")]
pub(crate) fn update_tray_display_mode(app: &AppHandle, mode: TrayDisplayMode) {
if let Err(error) = set_tray_display_mode(app, mode) {
eprintln!("Failed to update tray display mode: {error}");
Expand Down Expand Up @@ -186,6 +203,7 @@ fn apply_dock_display_mode<R: Runtime>(app: &AppHandle<R>, mode: DockDisplayMode
}
}

#[cfg(target_os = "macos")]
fn build_menu<R: Runtime>(app: &AppHandle<R>, settings: &AppSettings) -> tauri::Result<Menu<R>> {
let pkg_info = app.package_info();
let config = app.config();
Expand Down
3 changes: 2 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { SettingsModal } from "./components/SettingsModal";
import { finishForceClose, type DesktopReopenPreference } from "./lib/desktopReopen";
import type { CodexClosePreference } from "./lib/codexClosePreference";
import { useForceCloseCodexProcesses } from "./hooks/useForceCloseCodexProcesses";
import { AccountCard, AddAccountModal, UpdateChecker } from "./components";
import { AccountCard, AddAccountModal, UpdateChecker, WindowResizeBorders } from "./components";
import type { AccountWithUsage, CodexProcessInfo, DockDisplayMode, UsageInfo } from "./types";
import {
exportFullBackupFile,
Expand Down Expand Up @@ -1349,6 +1349,7 @@ function App() {

return (
<div className="min-h-screen bg-gray-50 text-gray-900 dark:bg-gray-950 dark:text-gray-100">
<WindowResizeBorders isMaximized={isWindowMaximized} />
<header className="sticky top-0 z-40 border-b border-gray-200 bg-white dark:border-gray-800 dark:bg-gray-900">
<div className="flex h-9 items-center bg-white px-3 dark:bg-gray-900">
<div
Expand Down
80 changes: 80 additions & 0 deletions src/components/WindowResizeBorders.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import React from "react";
import { isTauriRuntime } from "../lib/platform";
import { getTauriWindow } from "../lib/tauriWindow";

type ResizeDirection =
| "East"
| "North"
| "NorthEast"
| "NorthWest"
| "South"
| "SouthEast"
| "SouthWest"
| "West";

interface WindowResizeBordersProps {
isMaximized?: boolean;
}

const isMacOs =
typeof navigator !== "undefined" &&
/(Mac|iPhone|iPod|iPad)/i.test(navigator.userAgent);

export const WindowResizeBorders: React.FC<WindowResizeBordersProps> = ({
isMaximized = false,
}) => {
if (!isTauriRuntime() || isMacOs || isMaximized) {
return null;
}

const handleResizeStart =
(direction: ResizeDirection) => (event: React.MouseEvent) => {
if (event.button !== 0) return;
event.preventDefault();
event.stopPropagation();
void getTauriWindow()?.startResizeDragging(direction);
};

return (
<div
className="pointer-events-none fixed inset-0 z-50 overflow-hidden"
aria-hidden="true"
>
{/* Edges */}
<div
onMouseDown={handleResizeStart("North")}
className="pointer-events-auto absolute top-0 left-3 right-3 h-1.5 cursor-n-resize select-none"
/>
<div
onMouseDown={handleResizeStart("South")}
className="pointer-events-auto absolute bottom-0 left-3 right-3 h-1.5 cursor-s-resize select-none"
/>
<div
onMouseDown={handleResizeStart("West")}
className="pointer-events-auto absolute top-3 bottom-3 left-0 w-1.5 cursor-w-resize select-none"
/>
<div
onMouseDown={handleResizeStart("East")}
className="pointer-events-auto absolute top-3 bottom-3 right-0 w-1.5 cursor-e-resize select-none"
/>

{/* Corners */}
<div
onMouseDown={handleResizeStart("NorthWest")}
className="pointer-events-auto absolute top-0 left-0 h-3 w-3 cursor-nw-resize select-none"
/>
<div
onMouseDown={handleResizeStart("NorthEast")}
className="pointer-events-auto absolute top-0 right-0 h-3 w-3 cursor-ne-resize select-none"
/>
<div
onMouseDown={handleResizeStart("SouthWest")}
className="pointer-events-auto absolute bottom-0 left-0 h-3 w-3 cursor-sw-resize select-none"
/>
<div
onMouseDown={handleResizeStart("SouthEast")}
className="pointer-events-auto absolute bottom-0 right-0 h-3 w-3 cursor-se-resize select-none"
/>
</div>
);
};
1 change: 1 addition & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export { UsageBar } from "./UsageBar";
export { AddAccountModal } from "./AddAccountModal";
export { AccountUsageStats } from "./AccountUsageStats";
export { UpdateChecker } from "./UpdateChecker";
export { WindowResizeBorders } from "./WindowResizeBorders";
Loading