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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
/target
.superpowers/
.claude/settings.local.json

# `tauri icon` side-products for platforms this app doesn't ship on
crates/openwith-gui/src-tauri/icons/ios/
crates/openwith-gui/src-tauri/icons/android/
3 changes: 2 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ crates/
- GUI: single `get_snapshot` command returns apps + associations (with sibling-UTI conflict data) + contested schemes in one call; the frontend is a plain render-to-innerHTML loop with `data-action` event delegation, no framework. Versions are lockstep: `tauri.conf.json` omits `version` so the app version comes from `workspace.package` in the root Cargo.toml.
- `openwith-core::history` is the shared change log (capped at 500 events, best-effort writes that never fail the triggering change). CLI, GUI, and core import all record into it; the GUI Profiles panel shows export/import events, the menu-bar popover shows set events with per-entry Undo, and `openwith history`/`openwith undo` read the same file.
- The GUI is two windows off one Vite bundle: `main` and a hidden transparent `menubar` popover (requires `macOSPrivateApi: true`). The popover hides on blur and is toggled by the tray icon or a configurable global shortcut (default ⌥⌘O; `set_toggle_shortcut` swaps the registration at runtime, the saved accelerator is re-applied at bootstrap). A **Pin** button suspends hide-on-blur for one showing (backend `PopoverPinned` AtomicBool, reset on every toggle) so a file can be dragged in from Finder — without it the click into Finder blurs and hides the panel. Focus events are unreliable for the transparent panel, so the backend emits `popover-shown` on every open and the popover refreshes from that, not just from focus. A backend `AppsCache` (refreshed by `get_snapshot`) keeps popover lookups instant.
- App icons come from the 2026-07 logo renders in `artifacts/` (`logo-mono-glyph.png`, `logo-icon-dark.png`, `logo-icon-light.png` — AI renders with **no alpha**, so masking is scripted, not manual): light → 1024 master → `tauri icon` set; dark → `icons/icon-dark.png`; mono glyph → `icons/tray-template.png` (64×44 black+alpha template, glyph ≈34px tall). The Dock icon follows the app's *resolved* appearance at runtime — `set_dock_icon_dark` swaps `NSApplication.applicationIconImage` between `icon.png`/`icon-dark.png` (macOS only re-renders bundle icons for the system appearance), invoked from `theme.ts` on every theme change. `icon.icns` is **hand-built**: the ≤64px slots use a legibility variant (header dots inpainted away, no shadow, larger glyph), so rerunning `tauri icon` clobbers it. All of these regenerate with `uv run scripts/gen-icons.py` (run it *after* any `tauri icon` invocation; it prints the `tauri icon` command for the PNG set).
- GUI settings live in localStorage (`openwith.settings`). The Settings pane mirrors the design prototype's full layout; controls whose feature ships in a later 0.5.x phase (launch at login, menu bar) render disabled with an "arrives in v0.5.1" note rather than as silently-dead toggles.
- GUI visual source of truth is the claude.design prototype "OpenWith GUI Explorations" (project 14225854-984c-4e5c-8d2b-8c9ce38a1624), variants 1c (light) / 2a (dark): glyph tab icons (⌸ ⊞ ⤴ ⇅ — never emoji), 2-char initial chips (20px rows / 26px app list / 52px detail), fixed mid accent oklch(0.62 0.14 45) for tab underline + toggles, inverted toast. Check UI changes against it before shipping.

Expand Down Expand Up @@ -160,7 +161,7 @@ Run against the built .app (not just `tauri dev`) before tagging any release wit
- [ ] Close the main window, reopen via Dock click AND via popover "Open main window" — repeat ×3
- [ ] Toggle "Show in menu bar" off/on ×3 — exactly one tray icon at every step
- [ ] Hide Dock icon on/off; then turn the tray off while the Dock is hidden — Dock icon must come back
- [ ] Appearance: flip System/Light/Dark with the popover open — both windows restyle
- [ ] Appearance: flip System/Light/Dark with the popover open — both windows restyle AND the Dock icon flips light/dark (System follows macOS)
- [ ] Every Settings toggle: launch at login, confirm before applying, warn on UTI conflicts, show bundle IDs, relaunch Finder, check automatically, channel, open-on-tab
- [ ] Set a default from the Extensions sheet; verify with `openwith current <ext>`; Undo from the toast; verify again
- [ ] Toasts dismiss themselves (~5s, ~8s with an Undo button) without being replaced
Expand Down
31 changes: 28 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ resolver = "2"
members = ["crates/openwith-core", "crates/openwith-cli", "crates/openwith-gui/src-tauri"]

[workspace.package]
version = "0.5.3"
version = "0.5.4"
edition = "2024"
license = "MIT"
repository = "https://github.com/ColeMei/openwith"
Expand Down
Binary file added artifacts/logo-icon-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added artifacts/logo-icon-light.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added artifacts/logo-mono-glyph.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion crates/openwith-gui/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "openwith-gui",
"private": true,
"version": "0.5.3",
"version": "0.5.4",
"type": "module",
"scripts": {
"dev": "vite",
Expand Down
7 changes: 7 additions & 0 deletions crates/openwith-gui/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,10 @@ tauri-plugin-positioner = { version = "2", features = ["tray-icon"] }
tauri-plugin-autostart = "2"
serde.workspace = true
serde_json.workspace = true

# Runtime Dock-icon swap (NSApplication.applicationIconImage); same versions
# tauri already pulls in transitively.
[target.'cfg(target_os = "macos")'.dependencies]
objc2 = "0.6"
objc2-app-kit = { version = "0.3", features = ["NSApplication", "NSImage", "NSResponder"] }
objc2-foundation = { version = "0.3", features = ["NSData"] }
3 changes: 2 additions & 1 deletion crates/openwith-gui/src-tauri/capabilities/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"autostart:allow-is-enabled",
"core:window:allow-hide",
"core:window:allow-show",
"core:window:allow-set-focus"
"core:window:allow-set-focus",
"core:window:allow-start-dragging"
]
}
Binary file modified crates/openwith-gui/src-tauri/icons/128x128.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified crates/openwith-gui/src-tauri/icons/128x128@2x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified crates/openwith-gui/src-tauri/icons/32x32.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified crates/openwith-gui/src-tauri/icons/64x64.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified crates/openwith-gui/src-tauri/icons/Square107x107Logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified crates/openwith-gui/src-tauri/icons/Square142x142Logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified crates/openwith-gui/src-tauri/icons/Square150x150Logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified crates/openwith-gui/src-tauri/icons/Square284x284Logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified crates/openwith-gui/src-tauri/icons/Square30x30Logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified crates/openwith-gui/src-tauri/icons/Square310x310Logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified crates/openwith-gui/src-tauri/icons/Square44x44Logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified crates/openwith-gui/src-tauri/icons/Square71x71Logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified crates/openwith-gui/src-tauri/icons/Square89x89Logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified crates/openwith-gui/src-tauri/icons/StoreLogo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified crates/openwith-gui/src-tauri/icons/icon.icns
Binary file not shown.
Binary file modified crates/openwith-gui/src-tauri/icons/icon.ico
Binary file not shown.
Binary file modified crates/openwith-gui/src-tauri/icons/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified crates/openwith-gui/src-tauri/icons/tray-template.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 5 additions & 4 deletions crates/openwith-gui/src-tauri/icons/tray-template.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
24 changes: 24 additions & 0 deletions crates/openwith-gui/src-tauri/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,30 @@ pub fn set_dock_visible(app: AppHandle, visible: bool) -> Result<(), String> {
Ok(())
}

/// Swap the Dock icon between the light and dark masters. macOS only
/// re-renders bundle icons for the *system* appearance, so following the
/// in-app Appearance setting has to happen at runtime. Driven by
/// theme.ts whenever the resolved theme changes.
#[tauri::command]
pub fn set_dock_icon_dark(app: AppHandle, dark: bool) -> Result<(), String> {
app.run_on_main_thread(move || {
use objc2::{AllocAnyThread, MainThreadMarker};
use objc2_app_kit::{NSApplication, NSImage};
use objc2_foundation::NSData;

static LIGHT: &[u8] = include_bytes!("../icons/icon.png");
static DARK: &[u8] = include_bytes!("../icons/icon-dark.png");
let mtm = MainThreadMarker::new().expect("run_on_main_thread runs on main");
let data = NSData::with_bytes(if dark { DARK } else { LIGHT });
if let Some(image) = NSImage::initWithData(NSImage::alloc(), &data) {
unsafe {
NSApplication::sharedApplication(mtm).setApplicationIconImage(Some(&image));
}
}
})
.map_err(|e| e.to_string())
}

#[derive(Serialize)]
pub struct HistoryEventDto {
pub kind: String,
Expand Down
1 change: 1 addition & 0 deletions crates/openwith-gui/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ pub fn run() {
commands::quit_app,
commands::set_tray_enabled,
commands::set_dock_visible,
commands::set_dock_icon_dark,
commands::set_popover_pinned,
commands::set_toggle_shortcut,
])
Expand Down
2 changes: 2 additions & 0 deletions crates/openwith-gui/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ export const api = {
invoke<void>("set_tray_enabled", { enabled }),
setDockVisible: (visible: boolean) =>
invoke<void>("set_dock_visible", { visible }),
setDockIconDark: (dark: boolean) =>
invoke<void>("set_dock_icon_dark", { dark }),
setPopoverPinned: (pinned: boolean) =>
invoke<void>("set_popover_pinned", { pinned }),
setToggleShortcut: (accelerator: string) =>
Expand Down
7 changes: 5 additions & 2 deletions crates/openwith-gui/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,12 @@ function renderHeader(): string {
</button>`,
).join("");

// data-tauri-drag-region only fires on the element itself, not children:
// it must sit on every surface that should drag the window (bar + wordmark),
// while the tab buttons stay grabbable-free by simply not carrying it.
return `
<div class="header">
<span style="font-size:13px;font-weight:700">OpenWith</span>
<div class="header" data-tauri-drag-region>
<span data-tauri-drag-region style="font-size:13px;font-weight:700">OpenWith</span>
<div class="tabs">
${tabs}
<button class="gear ${state.settingsOpen ? "active" : ""}" data-action="settings-toggle" title="Settings">⚙</button>
Expand Down
6 changes: 3 additions & 3 deletions crates/openwith-gui/src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -147,22 +147,22 @@ body {
font-family: ui-monospace, Menlo, monospace;
}

/* header + tabs; left padding clears the native overlay traffic lights */
/* header + tabs; left padding clears the native overlay traffic lights.
Window dragging comes from data-tauri-drag-region in the markup —
-webkit-app-region is Electron-only and does nothing in WKWebView. */
.header {
flex: none;
display: flex;
align-items: center;
gap: 8px;
padding: 14px 18px 12px 84px;
-webkit-app-region: drag;
}

.header .tabs {
margin-left: auto;
display: flex;
gap: 16px;
align-items: baseline;
-webkit-app-region: no-drag;
}

.tab {
Expand Down
6 changes: 6 additions & 0 deletions crates/openwith-gui/src/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
* settings; the resolved theme is stamped as data-theme on <html>, which
* styles.css keys its dark palette off. */

import { api } from "./api";

const SETTINGS_KEY = "openwith.settings";

export type Appearance = "system" | "light" | "dark";
Expand All @@ -25,6 +27,10 @@ export function applyTheme(): void {
const dark =
appearance === "system" ? systemDark.matches : appearance === "dark";
document.documentElement.dataset.theme = dark ? "dark" : "light";
// The Dock icon follows the resolved theme too — macOS only swaps bundle
// icons for the *system* appearance, so the app does it itself. Both
// windows call this on a theme change; the swap is idempotent.
api.setDockIconDark(dark).catch(() => {});
}

// Follow the OS live while the setting is "system".
Expand Down
Loading
Loading