diff --git a/build.zig.zon b/build.zig.zon index b4dc58efb..b164175c8 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -20,8 +20,8 @@ }, .z2d = .{ // vancluever/z2d - .url = "https://github.com/vancluever/z2d/archive/refs/tags/v0.7.1.tar.gz", - .hash = "z2d-0.7.1-j5P_HhFQDQC6T5srG235r_nQpCrNwI15Gu2zqVrtUxN5", + .url = "https://github.com/vancluever/z2d/archive/refs/tags/v0.7.2.tar.gz", + .hash = "z2d-0.7.2-j5P_Hm1oDQDQsWpGfSCMARhowBnuTHlQ_sBfij6TuG7l", .lazy = true, }, .zig_objc = .{ diff --git a/build.zig.zon.json b/build.zig.zon.json index 8b35e80e9..e1773f575 100644 --- a/build.zig.zon.json +++ b/build.zig.zon.json @@ -139,10 +139,10 @@ "url": "https://deps.files.ghostty.org/wuffs-122037b39d577ec2db3fd7b2130e7b69ef6cc1807d68607a7c232c958315d381b5cd.tar.gz", "hash": "sha256-nkzSCr6W5sTG7enDBXEIhgEm574uLD41UVR2wlC+HBM=" }, - "z2d-0.7.1-j5P_HhFQDQC6T5srG235r_nQpCrNwI15Gu2zqVrtUxN5": { + "z2d-0.7.2-j5P_Hm1oDQDQsWpGfSCMARhowBnuTHlQ_sBfij6TuG7l": { "name": "z2d", - "url": "https://github.com/vancluever/z2d/archive/refs/tags/v0.7.1.tar.gz", - "hash": "sha256-6ZqgrO/bcjgnuQcfq89VYptUUodsErM8Fz6nwBZhTJs=" + "url": "https://github.com/vancluever/z2d/archive/refs/tags/v0.7.2.tar.gz", + "hash": "sha256-tWrLlEOU4/0ZOlzgGOXB08fW7sqfyAFf3rlv0wl9b/c=" }, "zf-0.10.3-OIRy8aiIAACLrBllz0zjxaH0aOe5oNm3KtEMyCntST-9": { "name": "zf", diff --git a/build.zig.zon.nix b/build.zig.zon.nix index 50f2438c1..2d5fd8355 100644 --- a/build.zig.zon.nix +++ b/build.zig.zon.nix @@ -306,11 +306,11 @@ in }; } { - name = "z2d-0.7.1-j5P_HhFQDQC6T5srG235r_nQpCrNwI15Gu2zqVrtUxN5"; + name = "z2d-0.7.2-j5P_Hm1oDQDQsWpGfSCMARhowBnuTHlQ_sBfij6TuG7l"; path = fetchZigArtifact { name = "z2d"; - url = "https://github.com/vancluever/z2d/archive/refs/tags/v0.7.1.tar.gz"; - hash = "sha256-6ZqgrO/bcjgnuQcfq89VYptUUodsErM8Fz6nwBZhTJs="; + url = "https://github.com/vancluever/z2d/archive/refs/tags/v0.7.2.tar.gz"; + hash = "sha256-tWrLlEOU4/0ZOlzgGOXB08fW7sqfyAFf3rlv0wl9b/c="; }; } { diff --git a/build.zig.zon.txt b/build.zig.zon.txt index 4a22d98df..88a0ad813 100644 --- a/build.zig.zon.txt +++ b/build.zig.zon.txt @@ -33,4 +33,4 @@ https://github.com/mbadolato/iTerm2-Color-Schemes/archive/8b639f0c2605557bd23ba1 https://github.com/mitchellh/libxev/archive/7f803181b158a10fec8619f793e3b4df515566cb.tar.gz https://github.com/mitchellh/zig-objc/archive/c9e917a4e15a983b672ca779c7985d738a2d517c.tar.gz https://github.com/natecraddock/zf/archive/7aacbe6d155d64d15937ca95ca6c014905eb531f.tar.gz -https://github.com/vancluever/z2d/archive/refs/tags/v0.7.1.tar.gz +https://github.com/vancluever/z2d/archive/refs/tags/v0.7.2.tar.gz diff --git a/flatpak/zig-packages.json b/flatpak/zig-packages.json index 02d5ea1c7..80dcf4bc1 100644 --- a/flatpak/zig-packages.json +++ b/flatpak/zig-packages.json @@ -169,9 +169,9 @@ }, { "type": "archive", - "url": "https://github.com/vancluever/z2d/archive/refs/tags/v0.7.1.tar.gz", - "dest": "vendor/p/z2d-0.7.1-j5P_HhFQDQC6T5srG235r_nQpCrNwI15Gu2zqVrtUxN5", - "sha256": "e99aa0acefdb723827b9071fabcf55629b5452876c12b33c173ea7c016614c9b" + "url": "https://github.com/vancluever/z2d/archive/refs/tags/v0.7.2.tar.gz", + "dest": "vendor/p/z2d-0.7.2-j5P_Hm1oDQDQsWpGfSCMARhowBnuTHlQ_sBfij6TuG7l", + "sha256": "b56acb944394e3fd193a5ce018e5c1d3c7d6eeca9fc8015fdeb96fd3097d6ff7" }, { "type": "archive", diff --git a/macos/Sources/Features/QuickTerminal/QuickTerminalController.swift b/macos/Sources/Features/QuickTerminal/QuickTerminalController.swift index 3bd8bc18f..1f608f767 100644 --- a/macos/Sources/Features/QuickTerminal/QuickTerminalController.swift +++ b/macos/Sources/Features/QuickTerminal/QuickTerminalController.swift @@ -111,12 +111,27 @@ class QuickTerminalController: BaseTerminalController { // Setup our initial size based on our configured position position.setLoaded(window) + // Upon first adding this Window to its host view, older SwiftUI + // seems to have a "hiccup" and corrupts the frameRect, + // sometimes setting the size to zero, sometimes corrupting it. + // We pass the actual window's frame as "initial" frame directly + // to the window, so it can use that instead of the frameworks + // "interpretation" + if let qtWindow = window as? QuickTerminalWindow { + qtWindow.initialFrame = window.frame + } + // Setup our content window.contentView = NSHostingView(rootView: TerminalView( ghostty: self.ghostty, viewModel: self, delegate: self )) + + // Clear out our frame at this point, the fixup from above is complete. + if let qtWindow = window as? QuickTerminalWindow { + qtWindow.initialFrame = nil + } // Animate the window in animateIn() diff --git a/macos/Sources/Features/QuickTerminal/QuickTerminalWindow.swift b/macos/Sources/Features/QuickTerminal/QuickTerminalWindow.swift index 005808a23..1a4170dbc 100644 --- a/macos/Sources/Features/QuickTerminal/QuickTerminalWindow.swift +++ b/macos/Sources/Features/QuickTerminal/QuickTerminalWindow.swift @@ -29,4 +29,19 @@ class QuickTerminalWindow: NSPanel { // We don't want to activate the owning app when quick terminal is triggered. self.styleMask.insert(.nonactivatingPanel) } + + /// This is set to the frame prior to setting `contentView`. This is purely a hack to workaround + /// bugs in older macOS versions (Ventura): https://github.com/ghostty-org/ghostty/pull/8026 + var initialFrame: NSRect? = nil + + override func setFrame(_ frameRect: NSRect, display flag: Bool) { + // Upon first adding this Window to its host view, older SwiftUI + // seems to have a "hiccup" and corrupts the frameRect, + // sometimes setting the size to zero, sometimes corrupting it. + // If we find we have cached the "initial" frame, use that instead + // the propagated one through the framework + // + // https://github.com/ghostty-org/ghostty/pull/8026 + super.setFrame(initialFrame ?? frameRect, display: flag) + } } diff --git a/macos/Sources/Features/Terminal/BaseTerminalController.swift b/macos/Sources/Features/Terminal/BaseTerminalController.swift index c93a9450d..ffe7997a6 100644 --- a/macos/Sources/Features/Terminal/BaseTerminalController.swift +++ b/macos/Sources/Features/Terminal/BaseTerminalController.swift @@ -290,8 +290,12 @@ class BaseTerminalController: NSWindowController, alert.addButton(withTitle: "Cancel") alert.alertStyle = .warning alert.beginSheetModal(for: window) { response in + let alertWindow = alert.window self.alert = nil if response == .alertFirstButtonReturn { + // This is important so that we avoid losing focus when Stage + // Manager is used (#8336) + alertWindow.orderOut(nil) completion() } } diff --git a/macos/Sources/Features/Terminal/TerminalController.swift b/macos/Sources/Features/Terminal/TerminalController.swift index ec56fb934..644a0c8ac 100644 --- a/macos/Sources/Features/Terminal/TerminalController.swift +++ b/macos/Sources/Features/Terminal/TerminalController.swift @@ -196,7 +196,12 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr if let parent { if parent.styleMask.contains(.fullScreen) { - parent.toggleFullScreen(nil) + // If our previous window was fullscreen then we want our new window to + // be fullscreen. This behavior actually doesn't match the native tabbing + // behavior of macOS apps where new windows create tabs when in native + // fullscreen but this is how we've always done it. This matches iTerm2 + // behavior. + c.toggleFullscreen(mode: .native) } else if ghostty.config.windowFullscreen { switch (ghostty.config.windowFullscreenMode) { case .native: @@ -747,6 +752,9 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr alert.alertStyle = .warning alert.beginSheetModal(for: alertWindow, completionHandler: { response in if (response == .alertFirstButtonReturn) { + // This is important so that we avoid losing focus when Stage + // Manager is used (#8336) + alert.window.orderOut(nil) closeAllWindowsImmediately() } }) diff --git a/po/es_AR.UTF-8.po b/po/es_AR.UTF-8.po index 16f955355..496e0ebbf 100644 --- a/po/es_AR.UTF-8.po +++ b/po/es_AR.UTF-8.po @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: com.mitchellh.ghostty\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n" "POT-Creation-Date: 2025-07-22 17:18+0000\n" -"PO-Revision-Date: 2025-05-19 20:17-0300\n" +"PO-Revision-Date: 2025-08-22 09:35-0300\n" "Last-Translator: Alan Moyano \n" "Language-Team: Argentinian \n" "Language: es_AR\n" @@ -208,12 +208,12 @@ msgstr "Permitir" #: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:81 #: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:77 msgid "Remember choice for this split" -msgstr "" +msgstr "Recordar elección para esta división" #: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:82 #: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:78 msgid "Reload configuration to show this prompt again" -msgstr "" +msgstr "Recargar la configuración para volver a mostrar este mensaje" #: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:7 #: src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp:7 @@ -278,15 +278,15 @@ msgstr "Copiado al portapapeles" #: src/apprt/gtk/Surface.zig:1268 msgid "Cleared clipboard" -msgstr "" +msgstr "Portapapeles limpiado" #: src/apprt/gtk/Surface.zig:2525 msgid "Command succeeded" -msgstr "" +msgstr "Comando ejecutado correctamente" #: src/apprt/gtk/Surface.zig:2527 msgid "Command failed" -msgstr "" +msgstr "El comando ha fallado" #: src/apprt/gtk/Window.zig:216 msgid "Main Menu" diff --git a/src/Surface.zig b/src/Surface.zig index 2ab265cda..770c2daef 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -3555,26 +3555,7 @@ pub fn mouseButtonCallback( }; switch (self.config.right_click_action) { - .ignore => { - // Return early to skip clearing the selection. - try self.queueRender(); - return true; - }, - .copy => { - if (self.io.terminal.screen.selection) |sel| { - self.copySelectionToClipboards(sel, &.{.standard}); - } - }, - .@"copy-or-paste" => { - if (self.io.terminal.screen.selection) |sel| { - self.copySelectionToClipboards(sel, &.{.standard}); - } else { - try self.startClipboardRequest(.standard, .paste); - } - }, - .paste => { - try self.startClipboardRequest(.standard, .paste); - }, + .ignore => {}, .@"context-menu" => { // If we already have a selection and the selection contains // where we clicked then we don't want to modify the selection. @@ -3588,12 +3569,45 @@ pub fn mouseButtonCallback( const sel = screen.selectWord(pin) orelse break :sel; try self.setSelection(sel); try self.queueRender(); + + // Don't consume so that we show the context menu in apprt. return false; }, - } + .copy => { + if (self.io.terminal.screen.selection) |sel| { + self.copySelectionToClipboards(sel, &.{.standard}); + } - try self.setSelection(null); - try self.queueRender(); + try self.setSelection(null); + try self.queueRender(); + }, + .@"copy-or-paste" => if (self.io.terminal.screen.selection) |sel| { + self.copySelectionToClipboards(sel, &.{.standard}); + try self.setSelection(null); + try self.queueRender(); + } else { + // Pasting can trigger a lock grab in complete clipboard + // request so we need to unlock. + self.renderer_state.mutex.unlock(); + defer self.renderer_state.mutex.lock(); + try self.startClipboardRequest(.standard, .paste); + + // We don't need to clear selection because we didn't have + // one to begin with. + }, + .paste => { + // Before we yield the lock, clear our selection if we have + // one. + try self.setSelection(null); + try self.queueRender(); + + // Pasting can trigger a lock grab in complete clipboard + // request so we need to unlock. + self.renderer_state.mutex.unlock(); + defer self.renderer_state.mutex.lock(); + try self.startClipboardRequest(.standard, .paste); + }, + } // Consume the event such that the context menu is not displayed. return true; @@ -4494,19 +4508,32 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool const pos = try self.rt_surface.getCursorPos(); if (try self.linkAtPos(pos)) |link_info| { - // Get the URL text from selection - const url_text = (self.io.terminal.screen.selectionString(self.alloc, .{ - .sel = link_info[1], - .trim = self.config.clipboard_trim_trailing_spaces, - })) catch |err| { - log.err("error reading url string err={}", .{err}); - return false; + const url_text = switch (link_info[0]) { + .open => url_text: { + // For regex links, get the text from selection + break :url_text (self.io.terminal.screen.selectionString(self.alloc, .{ + .sel = link_info[1], + .trim = self.config.clipboard_trim_trailing_spaces, + })) catch |err| { + log.err("error reading url string err={}", .{err}); + return false; + }; + }, + + ._open_osc8 => url_text: { + // For OSC8 links, get the URI directly from hyperlink data + const uri = self.osc8URI(link_info[1].start()) orelse { + log.warn("failed to get URI for OSC8 hyperlink", .{}); + return false; + }; + break :url_text try self.alloc.dupeZ(u8, uri); + }, }; defer self.alloc.free(url_text); self.rt_surface.setClipboardString(url_text, .standard, false) catch |err| { log.err("error copying url to clipboard err={}", .{err}); - return true; + return false; }; return true; diff --git a/src/os/i18n.zig b/src/os/i18n.zig index c6bce6fbf..29f7f6bc3 100644 --- a/src/os/i18n.zig +++ b/src/os/i18n.zig @@ -136,7 +136,12 @@ pub fn canonicalizeLocale( buf: []u8, locale: []const u8, ) error{NoSpaceLeft}![:0]const u8 { - if (comptime !build_config.i18n) return locale; + if (comptime !build_config.i18n) { + if (buf.len < locale.len + 1) return error.NoSpaceLeft; + @memcpy(buf[0..locale.len], locale); + buf[locale.len] = 0; + return buf[0..locale.len :0]; + } // Fix zh locales for macOS if (fixZhLocale(locale)) |fixed| {