v1.0 ghostty action coverage: batches 1-4 (58/65 wired)#58
Open
i999rri wants to merge 39 commits into
Open
Conversation
Without this the terminal BEL character (\x07) is silent and the user has no audible cue when an interactive program emits a bell — backwards from the typical terminal experience and inconsistent with Mac / GTK ghostty which do produce a sound. MessageBeep(MB_OK) is the native Windows path: the OS plays the sound bound to the user's "Default Beep" entry in Sound settings, which is the conventional place for this and lets each user pick the volume / sound they prefer without an app-side config knob. The call is asynchronous and thread-safe so it runs straight from action_cb on the renderer thread. Honouring ghostty's `bell-features` (audio / attention / title / unread) granularly is a follow-up — this PR ships the audio component which is the one users actually notice.
Without these the ghostty side could request the host to terminate the app (quit keybind, command palette quit, etc.) and nothing happened — the user had to use the title-bar close button instead. In a single-window build the three lifecycle actions collapse to the same thing: close the lone window, which terminates the app. Multi-window support (#55) will need to split them back out (`CLOSE_WINDOW` = close active, `CLOSE_ALL_WINDOWS` = close every window, `QUIT` = shut down the process with whatever multi-window quit confirmation we adopt). Dispatch to the UI thread because action_cb fires from the renderer thread but Close() touches XAML state.
Without this the ghostty keybind that toggles maximize did nothing — only the caption button click path could maximize / restore the window, so keyboard-driven users had to fall back to Win+arrow or alt-space. Route the action through the same WM_SYSCOMMAND SC_MAXIMIZE / SC_RESTORE the caption-button click sends. Going via WM_SYSCOMMAND (instead of OverlappedPresenter.Maximize / Restore) keeps us off the NVIDIA driver AV codepath documented in issue #26. Dispatched to the UI thread because action_cb is called from the renderer thread and SendMessage / IsZoomed expect the message loop owner.
Without this the ghostty open-config keybind (Cmd/Ctrl+, in default bindings) silently does nothing on Windows — the user has to open `%LOCALAPPDATA%\ghostty\config` manually in a file manager / editor every time they want to tweak settings. Pass the absolute config path to ShellExecuteW with the "open" verb so Windows picks whatever editor the user has associated with extension-less plain-text files. First-run users with no association get the system "Open With" dialog, which is the right OS-native discovery flow.
Without this the ghostty copy-title keybind silently did nothing on Windows. Useful when the title contains the cwd / running command and the user wants to paste it elsewhere. Pull the title back out of the TabViewItem.Header that SET_TITLE / SET_TAB_TITLE already populates and send it through the same Clipboard::write helper the selection-copy path uses. Empty titles are skipped so we don't blow away whatever the user already had on the clipboard.
Without this the ghostty float-window keybind silently dropped on Windows even though the underlying primitive (WS_EX_TOPMOST) has been there since Win32 day one. Users who keep a small ghostty pane on top of a video / chat window needed to alt-Tab back and forth instead. SetWindowPos with HWND_TOPMOST / HWND_NOTOPMOST + SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE flips the z-order flag in place without resizing or repositioning the window. The TOGGLE variant reads the current WS_EX_TOPMOST bit so the action follows the window's actual state rather than a host-side cached one.
Without this the ghostty toggle-visibility keybind silently dropped. The action is the building block for hide-on-blur / hotkey-hide workflows: press the bind to hide the window, press it again to bring it back. ShowWindow(SW_HIDE) / ShowWindow(SW_SHOW) is the native Win32 idiom; SetForegroundWindow on the show branch raises the window to the foreground because the user just asked for it back. Note that the keybind needs the `global:` qualifier in the user's config for the show side to work — without that the bind only fires when the window has keyboard focus, so a hidden window can't be brought back via the bind alone (taskbar / alt-tab still work).
MSVC /W4 flags _wgetenv as deprecated (C4996) which the project build treats as an error. Swapping the CRT helper for the Win32 GetEnvironmentVariableW path is the documented modern fix: the API writes into a caller buffer so there's no heap-allocation cleanup, and it doesn't trip the security-deprecation warning.
Static analyzer (C6011) flagged the post-GlobalLock memcpy as a possible null deref. GlobalLock can fail under low memory or if the handle was invalidated between alloc and lock; the existing code assumed success and called memcpy with the null `dest`. Bail out cleanly when the lock fails: GlobalFree the allocation (SetClipboardData would otherwise inherit an unwritten buffer and the OS would own a corrupted handle) and CloseClipboard so we don't leave the clipboard locked from other apps. Surfaced now because the new COPY_TITLE_TO_CLIPBOARD path covers a code path the analyzer hadn't previously walked.
User feedback: ShowWindow(SW_HIDE) on Ctrl+Shift+H makes the window disappear from the taskbar too, leaving no discoverable way to bring it back. ghostty's `global:` keybind qualifier isn't wired to Win32 RegisterHotKey on this port yet, so SW_HIDE plus a non-global bind on the same key means the user has to relaunch the app to recover. Switch to SW_MINIMIZE / SW_RESTORE on IsIconic — the window stays in the taskbar, the bind continues to fire while focused, and the taskbar click is the natural restore path Windows users expect. Mac-style full hide can come back once global hotkeys land.
The verification round confirmed every batch-1 action (RING_BELL, TOGGLE_MAXIMIZE, CLOSE_WINDOW/QUIT, COPY_TITLE_TO_CLIPBOARD, OPEN_CONFIG, TOGGLE_VISIBILITY) arrives at action_cb and fires its handler. FLOAT_WINDOW didn't appear in traces because the Ctrl+Shift+F default bind to start_search took precedence in the user's config — that's a config concern, not a host code issue. Drops the per-action OutputDebugStringA + the <cstdio> include it required.
Without a host handler the default ctrl+shift+, keybind just bounces off action_cb, leaving the user no way to pick up a config edit short of a full app restart — annoying enough that we kept reaching for the restart button while iterating on keybind tests. Parse the fresh config on a 4MB-stack worker (the parser stack- overflows the UI thread's default 1MB, same constraint as the init path in GhosttyApp::Create), then hand the result to the UI thread where ghostty_app_tick lives. The swap step replaces the owned config and frees the old one after ghostty_app_update_config has applied the new pointer; soft reloads skip the parse and just re-apply the existing config. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three keybind attempts (ctrl+shift+f, ctrl+shift+alt+f, and ctrl+shift+backslash) all failed to reach action_cb: the first collides with the default start_search, the second is intercepted by the WinUI Alt-menu accelerator before ghostty sees it, and the third produced no observable action_cb call either despite no known conflict. Until the dispatch path is understood the handler is dead code; #if 0 guards it so a future test build can re-enable the body verbatim once the keybind side is sorted. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Without a host handler the action_cb default (return false) gives no visible breadcrumb when the generic renderer flips to its degraded path — texture allocation failures and shader faults are exactly the kind of issue that takes longer to diagnose if we only learn about them via downstream symptoms (missing glyphs, blank frames). Surface the health flip as a single stderr line; that's enough to make it findable in the debugger output without committing to a user-facing recovery UX yet, which is its own design problem. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The action only matters for apprts that keep a process alive after the last window closes (macOS-style "quit after countdown"). Windows exits as soon as the last top-level HWND is gone — CLOSE_WINDOW already triggers Close() — so START / STOP don't need wiring on either side. Make the ignore explicit so a future libghostty audit doesn't flag the action as falling through to the unhandled-action default. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Without a host handler the cursor stays parked over the terminal while the user types — distracting against a dark theme and out of step with the Vim / Helix / Kitty convention ghostty itself follows. Toggle ShowCursor on HIDDEN <-> VISIBLE transitions, but gate on a local bool: ShowCursor is a counter, so blindly calling it on every event would let the counter walk arbitrarily far from zero and break the next state change. Dispatching to the UI thread lines the call up with the same thread WM_MOUSEMOVE drives. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ghostty fires this after it has internally applied a new config — it's a notification, not a "please apply this" request. If the host ignores it the GhosttyApp-stored m_config drifts out of sync with what libghostty is actually running, which would silently break the RELOAD_CONFIG soft path (it would re-apply the stale copy) and lie to any future host-side config query (theme color, padding, etc.). Clone the incoming pointer because libghostty owns it, then swap on the UI thread the same way the reload_config flow does so the free of the previous config can't race a tick that's still reading it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Without a handler the action falls through to action_cb's default and the user's "reset window size" keybind silently does nothing, which is worse than picking a reasonable default — at least one makes the keybind feel alive. Snap the HWND back to 1280x720 DIPs, DPI-scaled. The truly correct restore target is whatever ghostty sent us via INITIAL_SIZE at startup, but that handler isn't wired yet (#57 still has it open); 1280x720 matches the WinUI fresh-window default and lands around 80x24 cells at our typical font sizes, which is close enough to "reset" semantics to be useful in the meantime. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous RESET_WINDOW_SIZE handler reset to a hardcoded 1280x720 DIPs, which means anyone with a config-driven startup size (window width/height in cells, custom fonts that yield non-default pixel dimensions) saw their window snap to the wrong footprint on reset. Record the dimensions ghostty hands us via INITIAL_SIZE (already expressed in physical pixels post cell-to-pixel math) and feed them straight to SetWindowPos when reset fires. Keep the 1280x720 fallback for the case where ghostty never sends INITIAL_SIZE — that path matches the old behavior and the WinUI fresh-window default. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ghostty fires this whenever the active font / DPI / config combination changes the per-cell pixel dimensions. Without caching it locally, any host-side logic that wants whole-cell rounding (snap-to-cell window resize, split ratio quantization, etc.) has to round-trip through libghostty every time the value is needed. Stash width and height on MainWindow so future features can read them straight off the view object. No behavior change today — nothing yet reads m_cellWidth / m_cellHeight, the wiring is the prerequisite for the features that will. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The terminal surface fills the available area without a scrollbar widget, so the total / offset / visible-len ghostty sends here has nowhere to render. Returning true keeps the action from falling through to action_cb's unhandled-default branch and avoids any future "unhandled action" log on the libghostty side. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ghostty asks for an explicit repaint when it wants a frame out sooner than the next natural tick — e.g., right after a config swap that changes colors and the user wants to see them immediately, not on the next keystroke. Without a handler that request gets dropped and the frame lags behind until something else wakes the loop. Route through the same UI-thread Tick path wakeup_cb uses, since ghostty_app_tick already handles the "render the dirty surfaces" job and is idempotent when nothing is dirty. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When a shell process exits unexpectedly during development the default (return false) drops the notification, and the only signal is the surface vanishing — no way to tell whether the shell quit cleanly or crashed without instrumenting the host with logging. Log the exit code and runtime to stderr so the next debugger run puts that breadcrumb in front of the developer. An in-terminal overlay would be the proper UI for users running with confirm-close-surface=true, but that's a separate design problem worth its own iteration. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Without a host handler the action falls through and the menu / keybind silently does nothing. We don't ship a Sparkle-style in-app updater on Windows yet — and writing one is its own project — so the lowest-friction substitute is to point the user at the releases page and let them grab the next build manually. Use ShellExecuteW so the user's default browser opens without the visible cmd / rundll32 flash that std.process.Child fallback would produce, matching the same path OPEN_URL takes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
OSC 9;4 progress reports (pnpm install, make, large copies, etc.) were being dropped — the shell sent percent-complete updates and nothing in the host surfaced them, so long-running operations gave no visual signal of progress outside the terminal itself. Map ghostty's state machine onto ITaskbarList3 so the percentage shows up on the taskbar button, which is the Windows-native surface for background-task progress. Cache the ITaskbarList3 instance on the UI thread (where COM is STA-initialized) because CoCreateInstance + HrInit per OSC sequence would be wasteful. Skip SetProgressValue when the state would make it meaningless (NOPROGRESS, INDETERMINATE) or when ghostty reports no percentage so the bar doesn't snap to 0% on bare state transitions. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GHOSTTY_ACTION_READONLY, SECURE_INPUT, KEY_SEQUENCE, KEY_TABLE, PROMPT_TITLE, PWD, and COMMAND_FINISHED all carry state libghostty wants the host to surface visually — read-only banner, secure-input padlock, pending-chord indicator, modal-table label, prompt-driven title source, PWD breadcrumb, post-command summary. None of those surfaces exist on our side yet and each is its own design pass. Bundle the seven into a single ack branch so they don't fall through to the unhandled-default and so the day-one wiring is in place when each gets its actual UI in a later PR. Keeping them in one branch keeps the action_cb body honest about which actions are "explicitly no-op for now" versus "deliberately implemented". Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Without a handler the OSC bell sequences and the shell-side "your build finished" notifications get dropped — the terminal beep (RING_BELL) is the only signal that something completed in the background. That's fine for a foreground session but loses tasks that finish while the user has switched windows. Register the AppNotificationManager on app launch so the manager is ready before any surface starts dispatching, then on each DESKTOP_NOTIFICATION build a two-line payload (title + body) and hand it to Show. The register call is in a try/catch because some MSIX activation paths reportedly throw on first run; failure mode is "toasts silently no-op" rather than a crash. The Show call also catches hresult_error so a transient OS refusal can't kill the renderer thread. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The default keybind for fullscreen sat idle without a handler — pressing it produced no visible change, leaving the only path to fullscreen the manual maximize button, which still draws the caption / taskbar chrome. Implement the borderless-fullscreen pattern: stash WINDOWPLACEMENT and GWL_STYLE on entry, swap WS_OVERLAPPEDWINDOW off and resize to the monitor rect, then restore both on exit. Storing WINDOWPLACEMENT (not a plain RECT) preserves a maximised origin so toggling FS from maximised returns to maximised. The macOS-specific NON_NATIVE enum variants collapse to the same Win32 behavior. Known limitation: our custom title bar sits in the XAML content tree so it still draws on top of the swap chain in FS mode. Hiding it is a separate follow-up — the window itself does fill the monitor correctly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The hand-cursor change on link hover was already wired through MOUSE_SHAPE, but the URL itself stayed hidden — the user had to either trust the link blindly or Ctrl+click to find out where it points. That's a usability gap the rest of the desktop solves with a hover tooltip. Wire up a Win32 TOOLTIPS_CLASS popup in TRACK mode (manual positioning suits the DComp surface, which has no XAML hit-test geometry to attach a tooltip to). Lazy-init the window and the text-backing wstring as function-local statics so we don't churn the tooltip handle on every hover, and so TTM_UPDATETIPTEXT keeps referencing live memory. Empty url hides the tooltip; non-empty url positions it ~20px below-right of the cursor and shows the text. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Without a handler ghostty's min/max constraints (window-width / window-height capped in config, or shell-driven ratios) silently don't apply on Windows — the user can drag the window to any size, including below the minimum that breaks the rendered layout. WM_GETMINMAXINFO is the canonical hook for enforcing track-size limits in Win32, so install a SetWindowSubclass-based proc lazily on the first SIZE_LIMIT (no cost for apps that never set one) and let it clamp ptMin/MaxTrackSize against the cached limits. The subclass is a static MainWindow method so it has private-member access; dwRefData carries the MainWindow pointer so multiple windows (when we get there) wouldn't collide on a single global. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…undo) UNDO, REDO, the four SEARCH actions (START / END / TOTAL / SELECTED), INSPECTOR and its RENDER_INSPECTOR companion, plus the three overlay toggles (TAB_OVERVIEW, QUICK_TERMINAL, COMMAND_PALETTE) all dispatch correctly from libghostty but have no surface on our side yet — each is a multi-day feature (search bar, ImGui inspector port, three distinct overlay UIs, host-side undo stack) that deserves its own PR. Routing the eleven through a single ack today keeps the unhandled- default branch quiet so a future libghostty audit doesn't flag them as missing; the proper implementation work is still tracked under #57's feature backlog. Grouped separately from the existing acked- informational bundle (READONLY / SECURE_INPUT / KEY_SEQUENCE / etc.) because the intent is different: those are state notifications, these are feature toggles we'll genuinely wire up later. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Without these manifest entries AppNotificationManager.Register at startup throws "No COM servers are registered for this app" from deep inside WindowsAppRuntime's PushNotificationUtility, which our try/catch in App::OnLaunched did swallow — but it lit up the debugger every launch and left DESKTOP_NOTIFICATION silently broken (Show would fail the same way per call). Declare a CLSID via com:ComServer and tie it to the toast activation extension. We don't implement a custom IActivator factory — the AppNotifications projection handles activation through its own internal mechanism; what the manifest needs to see is the class registration plus the toastNotificationActivation hint pointing at the same CLSID. After this change Register returns success and Show actually surfaces toasts. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
After registering the COM activator, AppNotificationManager.Register moved past the "No COM servers" error but immediately failed deeper at PushNotificationUtility.h(256) with HRESULT 0x80070490 (ERROR_NOT_FOUND). Comparing against Microsoft's official packaged toast sample, the difference was the missing Arguments attribute on com:ExeServer: Windows uses that string to know how to launch the exe in "I was activated by a toast click" mode, and the registration flow validates its presence before completing. Add Arguments="----AppNotificationActivated:" so Register can finish. We still don't intercept that command-line in WinMain — a toast click currently spawns a new Ghostty instance instead of forwarding to the existing one — but that activation-routing UX is a separate problem; what this change buys is "Register succeeds and Show actually surfaces toasts." Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The original ShowCursor-based implementation looked correct in isolation but did nothing in practice: WinUI 3 owns the cursor through ProtectedCursor / InputSystemCursor, and its WM_SETCURSOR handler resets the cursor on every mouse move, immediately undoing the counter we decremented. The user reported the cursor not hiding while typing, and that's the reason. Install a WndProc subclass lazily on the first MOUSE_VISIBILITY and short-circuit WM_SETCURSOR with SetCursor(NULL) while m_cursorHidden is true. That's the only intercept point that beats XAML's cursor restoration. Flipping m_cursorHidden back to false lets the next mouse move — which is exactly the event that fires the VISIBLE side of the action — fall through to DefSubclassProc and restore the normal cursor. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Both actions came back as "doesn't work" during verification but the test paths can fail silently — RESET_WINDOW_SIZE when the new size happens to match the current size (no visible change), SHOW_CHILD_EXITED when the stderr buffer flushes after the surface teardown clobbers our chance to see it in the Output window. For RESET_WINDOW_SIZE, log the recorded INITIAL_SIZE values and the SetWindowPos result (including GetLastError on failure) so the next test confirms whether the handler runs and whether SetWindowPos actually succeeded. For SHOW_CHILD_EXITED, mirror the stderr print through OutputDebugStringA so the line shows up in VS's Debug output even when the renderer thread's stderr flush is interrupted by the surface closing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Both came back as "not working" during verification with no signal from inside the handler so far. The default for the gating config keys is the most likely culprit (mouse-hide-while-typing defaults to false, so MOUSE_VISIBILITY never fires unless the user opts in; link-url defaults to true so MOUSE_OVER_LINK should fire, but the hover detection might be silent if the URL substring doesn't match). Print the fire / payload size to both stderr and OutputDebugStringA so the next test confirms whether the action reaches us at all. Once we know that, we can pick the right next step — config nudge versus a real bug in the host-side handler. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Verification turned up a regression: clicking a URL with the tooltip code in place exits the process with code 3 immediately after ~16 [mouse_over_link] fires, even though the OPEN_URL path was shipping cleanly before this PR. The action handler itself is correct (the diagnostic line proves it), so the suspect is the tooltip popup interacting with the DComp surface's click routing — TTM_TRACKACTIVATE / SetWindowPos against a top-level TOPMOST popup on top of an HWND that's already a composition island isn't a combo I trust without more digging. #if 0 the tooltip body for now and ack the action with return true. URL click goes back to working, and the unrelated MOUSE_VISIBILITY / RESET_WINDOW_SIZE diagnostics aren't affected. The tooltip work is preserved verbatim for a follow-up PR once the click path is understood. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Verification turned up no [mouse_visibility] log after multiple restart-and-type tries with mouse-hide-while-typing=true in config. ghostty's firing condition in Surface.zig:2686 is event.action == .press && event.utf8.len > 0 && !self.mouse.hidden, and we already know typing does reach ghostty (the codepoint resolver runs), so the most plausible explanation is our ghostty_surface_key call site not populating event.utf8 for printable keystrokes. Even if that gets fixed, the host-side implementation uses a WM_SETCURSOR subclass that loses to WinUI's ProtectedCursor on every mouse move anyway. #if 0 the handler the same way FLOAT_WINDOW is guarded so the body survives verbatim for re-enable once #60 is unblocked. The header members and the CursorVisibilitySubclassProc declaration stay in place; nothing depends on them and rewinding the disable will be a single guard removal. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Those two prints were added to confirm whether the action handler was running and whether SetWindowPos succeeded. Verification's done (see PR #58 checklist), and this handler is otherwise quiet: in normal use the prints would just be noise in the debug output. Keep the [child_exited] + [renderer_health] stderr lines — those are genuine crash-diagnosis breadcrumbs that pay off the next time something abnormal happens. RESET_WINDOW_SIZE's success path doesn't have the same recovery value. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary / 概要
Closes a large slice of #57 (v1.0 ghostty action parity). Across four batches this PR takes action coverage from 14/65 to 58/65 wired — 25 new full implementations, 18 explicit acks, 1 disabled-pending-fix. The remaining 7 are either deferred to their own PRs (TOGGLE_WINDOW_DECORATIONS, TOGGLE_BACKGROUND_OPACITY), waiting on multi-window (#55: GOTO_WINDOW, MOVE_TAB, PRESENT_TERMINAL), or not applicable on Windows.
日本語
#57 (v1.0 ghostty action 対応) の大半を一気に片付ける PR。4 つのバッチで action カバレッジを 14/65 → 58/65 まで上げる (新規実装 25、明示 ack 18、無効化保留 1)。残り 7 は別 PR (
TOGGLE_WINDOW_DECORATIONS/TOGGLE_BACKGROUND_OPACITY)、multi-window #55 待ち (GOTO_WINDOW/MOVE_TAB/PRESENT_TERMINAL)、Windows 非対応 (SHOW_GTK_INSPECTOR/SHOW_ON_SCREEN_KEYBOARD)。What's in / 入っているもの
batch 1 — close / max / clipboard / config (8)
RING_BELLMessageBeep(MB_OK)CLOSE_WINDOW/CLOSE_ALL_WINDOWS/QUITWindow.Close()TOGGLE_MAXIMIZEWM_SYSCOMMAND+SC_MAXIMIZE/SC_RESTORECOPY_TITLE_TO_CLIPBOARDClipboard::writeOPEN_CONFIGShellExecuteW"open" on%LOCALAPPDATA%\ghostty\configTOGGLE_VISIBILITYShowWindow(SW_MINIMIZE / SW_RESTORE)SW_MINIMIZEkeeps the taskbar entry visiblebatch 2 — reload / mouse / health / size (6)
RELOAD_CONFIGghostty_config_load_default_files+ghostty_app_update_configctrl+shift+,keybind now worksCONFIG_CHANGEghostty_config_clone+ReplaceConfigRENDERER_HEALTHfprintf(stderr, ...)MOUSE_VISIBILITYShowCursorwith state-tracked toggleQUIT_TIMERRESET_WINDOW_SIZESetWindowPosto recordedINITIAL_SIZEor 1280x720 DIP fallbackbatch 3 — size / scrollbar / render / lifecycle (7)
INITIAL_SIZEm_initialWidth/m_initialHeight)window-width × cell-width-pxfrom config;RESET_WINDOW_SIZEconsumes itCELL_SIZEm_cellWidth/m_cellHeight)SCROLLBARRENDERTick()wakeup_cb;ghostty_app_tickis idempotent when nothing is dirtySHOW_CHILD_EXITEDfprintf(stderr, "[child_exited] exit_code=%u after_ms=%llu", ...)CHECK_FOR_UPDATESShellExecuteWto GitHub releasesPROGRESS_REPORTITaskbarList3::SetProgressState/SetProgressValuebatch 4 — high-priority Win32 (4)
DESKTOP_NOTIFICATIONMicrosoft.Windows.AppNotifications.AppNotificationBuilder+ShowApp::OnLaunched; Package.appxmanifest carries the COM activator + toast activation extension. Toast click currently spawns a second instance — separate follow-upTOGGLE_FULLSCREENSetWindowLongPtrW+SetWindowPos+WINDOWPLACEMENTsave/restoreMOUSE_OVER_LINKTOOLTIPS_CLASSpopup in TRACK modeSIZE_LIMITWM_GETMINMAXINFOviaSetWindowSubclassMainWindow*carried viadwRefDataAcknowledged (no behavior change, just
return true)Routing them through
action_cbso they don't fall through to the unhandled-default while the surface-side UI is built later.READONLY,SECURE_INPUT,KEY_SEQUENCE,KEY_TABLE,PROMPT_TITLE,PWD,COMMAND_FINISHEDUNDO,REDO,START_SEARCH,END_SEARCH,SEARCH_TOTAL,SEARCH_SELECTED,INSPECTOR,RENDER_INSPECTOR,TOGGLE_TAB_OVERVIEW,TOGGLE_QUICK_TERMINAL,TOGGLE_COMMAND_PALETTEDisabled —
#if 0-guarded (1)FLOAT_WINDOW—WS_EX_TOPMOSTtoggle written, verified Win32-side. No keybind we tried reachedaction_cb:ctrl+shift+fcollides with defaultstart_search;ctrl+shift+alt+fis swallowed by the WinUI Alt-menu accelerator;ctrl+shift+backslashproduced no observable call either. Body preserved verbatim; re-enable once the dispatch path is understood.日本語: 詳細
batch 1〜4 + ack バンドル + 無効化 FLOAT_WINDOW、計 22 コミット (Package.appxmanifest の toast activator 登録含む)。各 action のコメントは英語で残し、commit メッセージで「なぜ」を残す方針。
Test plan / 検証
Suggested keybinds (
%LOCALAPPDATA%\ghostty\config):RING_BELL:printf "\a"produces the OS bell soundCLOSE_WINDOW/QUIT: window closes via keybindTOGGLE_MAXIMIZE: keybind toggles maximize / restoreCOPY_TITLE_TO_CLIPBOARD: keybind puts the tab title on the clipboardOPEN_CONFIG:Ctrl+,opensconfigin the default editor (ghostty default)RELOAD_CONFIG: editbackground = 003366→ save →Ctrl+Shift+,→ background changes without restartCONFIG_CHANGE: covered by the reload test (no separate UI surface)— handlerMOUSE_VISIBILITY#if 0-guarded pending Windows keyboard input path drops fields: MOUSE_VISIBILITY + physical-key bindings don't fire #60 (action doesn't reachaction_cbon Windows; suspected cause:ghostty_surface_keynot populatingevent.utf8for printable keystrokes)RESET_WINDOW_SIZE: withkeybind = ctrl+alt+r=reset_window_size(the unicode-trigger workaround for Windows keyboard input path drops fields: MOUSE_VISIBILITY + physical-key bindings don't fire #60), drag the window to a custom size →Ctrl+Alt+Rsnaps it back to startup dimensions. Physical-key triggers likectrl+shift+digit_0don't fire because of the sameghostty_surface_keyfield-population issueTOGGLE_VISIBILITY:global:keybind hides → revealsTOGGLE_FULLSCREEN:F11→ borderless FS → F11 again → original window— tooltip bodyMOUSE_OVER_LINK#if 0-guarded pending MOUSE_OVER_LINK: Win32 tooltip crashes the process on URL click #61 (action reachesaction_cbbut the Win32TOOLTIPS_CLASStooltip crashed the process on URL click)SIZE_LIMIT: drag the window to its minimum → it stops at ghostty's hardcoded 10-cell × 4-row floor (the min is not user-configurable; ghostty sends the limit once at startup ascell_width * 10bycell_height * 4)DESKTOP_NOTIFICATION:echo "\e]9;hello`a"` in pwsh → Windows toast appears. Known limitation: clicking the toast spawns a second Ghostty instance (toast activation routing not wired yet — follow-up)PROGRESS_REPORT:echo "\e]9;4;1;50`a"→ taskbar button shows a progress bar;echo "`e]9;4;0`a"` clears itSHOW_CHILD_EXITED:exit 42in pwsh → VS Debug output shows[child_exited] exit_code=42 after_ms=...(mirrored throughOutputDebugStringAso it survives the surface teardown)CHECK_FOR_UPDATES: bind tokeybind = ctrl+shift+u=check_for_updates→ opens GitHub releases in browser日本語
検証用 keybind (
%LOCALAPPDATA%\ghostty\config):チェックリスト:
RING_BELL:printf "\a"で OS ベル音CLOSE_WINDOW/QUIT: キーバインドでウインドウが閉じるTOGGLE_MAXIMIZE: キーバインドで最大化 / 復元COPY_TITLE_TO_CLIPBOARD: キーバインドでタイトルがクリップボードへOPEN_CONFIG:Ctrl+,で config がデフォルトエディタで開くRELOAD_CONFIG: config を編集 → 保存 →Ctrl+Shift+,→ 再起動なしで色が変わるCONFIG_CHANGE: 上の reload テストでカバー— Windows keyboard input path drops fields: MOUSE_VISIBILITY + physical-key bindings don't fire #60 待ちでMOUSE_VISIBILITY#if 0無効化 (Windows で action_cb まで届かない、ghostty_surface_keyのevent.utf8populate 漏れが疑われる)RESET_WINDOW_SIZE:keybind = ctrl+alt+r=reset_window_size(Windows keyboard input path drops fields: MOUSE_VISIBILITY + physical-key bindings don't fire #60 の unicode-trigger workaround) で、サイズを変えてからCtrl+Alt+R押すと起動時サイズに戻る。物理キートリガー (ctrl+shift+digit_0等) は同じghostty_surface_keyのフィールド populate 問題で発火しないTOGGLE_VISIBILITY:global:付き keybind で隠す → 再表示TOGGLE_FULLSCREEN: F11 で全画面 → 再度 F11 で元に戻る— MOUSE_OVER_LINK: Win32 tooltip crashes the process on URL click #61 待ちで tooltip 本体をMOUSE_OVER_LINK#if 0無効化 (action は届くが Win32TOOLTIPS_CLASSツールチップが URL クリックでプロセスを落とす)SIZE_LIMIT: ウインドウを限界まで小さくドラッグ → ghostty の hardcoded な 10 セル × 4 行で止まる (min は user 指定不可、起動時にcell_width * 10×cell_height * 4で送られてくる)DESKTOP_NOTIFICATION: pwsh でecho "\e]9;hello`a"` → Windows のトースト。既知の制限: トーストをクリックすると Ghostty が二重起動 (activation routing 未対応、別 PR で)PROGRESS_REPORT:echo "\e]9;4;1;50`a"でタスクバーに進捗バー、echo "`e]9;4;0`a"` で消去SHOW_CHILD_EXITED: pwsh でexit 42→ VS のデバッグ出力に[child_exited] exit_code=42 after_ms=...(OutputDebugStringA経路でサーフェスのテアダウンを生き残る)CHECK_FOR_UPDATES:keybind = ctrl+shift+u=check_for_updatesで releases ページが開くOut of scope / スコープ外
TOGGLE_WINDOW_DECORATIONS— interacts with our custom XAML title bar; isolated PR for easier reviewTOGGLE_BACKGROUND_OPACITY— DComp swap chain blend mode; renderer change deserves its own PRWinMaincmdline parsing for----AppNotificationActivated:plus single-instance handoffINSPECTOR/RENDER_INSPECTOR— ImGui debug inspector port (Metal-on-macOS → DX-on-Windows)START_SEARCH/END_SEARCH/SEARCH_TOTAL/SEARCH_SELECTED— terminal search bar UITOGGLE_TAB_OVERVIEW/TOGGLE_QUICK_TERMINAL/TOGGLE_COMMAND_PALETTE— new overlay UIsUNDO/REDO— host-side undo stack (recently-closed tab restore, etc.)GOTO_WINDOW/MOVE_TAB/PRESENT_TERMINAL— multi-window (Multi-window support #55) dependentaction_cbitself into a dedicatedActionDispatcherclass — tracked in Refactor action_cb: move dispatch into a dedicated ActionDispatcher #59, lands after this merges日本語
TOGGLE_WINDOW_DECORATIONS: カスタムタイトルバー干渉、別 PRTOGGLE_BACKGROUND_OPACITY: DComp blend mode、renderer 変更で別 PRWinMainで----AppNotificationActivated:を見て single-instance に転送する処理が別途必要INSPECTOR/RENDER_INSPECTOR: ImGui inspector の Metal → DX 移植TAB_OVERVIEW/QUICK_TERMINAL/COMMAND_PALETTE): 各 overlay UIUNDO/REDO: host 側 undo stack (閉じたタブ復元等)action_cbのリファクタ (ActionDispatcherクラスへ抽出): Refactor action_cb: move dispatch into a dedicated ActionDispatcher #59、本 PR マージ後に着手🤖 Generated with Claude Code