Merge branch 'ghostty-org:main' into invertCursor

This commit is contained in:
alienman5k
2025-05-07 21:26:17 -07:00
committed by GitHub
81 changed files with 870 additions and 842 deletions

View File

@ -1,4 +1,4 @@
labels: ["needs confirmation"]
labels: ["needs-confirmation"]
body:
- type: markdown
attributes:
@ -14,22 +14,37 @@ body:
description: |
Provide a detailed description of the issue. Include relevant information, such as:
- The feature or configuration option you encounter the issue with.
- The expected behavior.
- The actual behavior (and how it deviates from the expected behavior, if it is not immediately obvious).
- Relevant Ghostty logs or other stacktraces.
- Relevant screenshots, screen recordings, or other supporting media (as needed).
- Screenshots, screen recordings, or other supporting media (as needed).
- If this is a regression of an existing issue that was closed or resolved, please include the previous item reference (Discussion, Issue, PR, commit) in your description.
>[!TIP]
> [!TIP]
> **Not sure what information to include?**
> Here are some recommendations:
> - **Input issues:** include your keyboard layout, a screenshot of the terminal inspector's logged keystrokes (Linux: <kbd>ctrl</kbd>+<kbd>shift</kbd>+<kbd>i</kbd>; MacOS: <kbd>cmd</kbd>+<kbd>alt</kbd>+<kbd>i</kbd>), input method, Linux input method engine (IBus, Fcitx 5, or none) and its version.
> - **Input issues:** include your keyboard layout, a screenshot of logged keystrokes from the terminal inspector's "Keyboard" tab (Linux: <kbd>ctrl</kbd>+<kbd>shift</kbd>+<kbd>i</kbd>; MacOS: <kbd>cmd</kbd>+<kbd>alt</kbd>+<kbd>i</kbd>), input method, Linux input method engine (IBus, Fcitx 5, or none) and its version.
> - **Font issues:** include the problematic character(s), the output of `ghostty +show-face` for these character(s), and if they work in other applications.
> - **VT issues (including image rendering issues):** attach an [asciinema](https://docs.asciinema.org/getting-started/) cast file, shell script, or text file for reproduction.
> - **Terminal emulation issues (including image rendering issues):** attach an [asciinema](https://docs.asciinema.org/getting-started/) cast file, shell script, or text file for reproduction.
> - **Renderer issues:** (Linux) include your OpenGL version, graphics card, driver version.
> - **Crashes:** (macOS) include the [Sentry UUID](https://github.com/ghostty-org/ghostty?tab=readme-ov-file#crash-reports); (Linux) try to reproduce using a debug build and provide the stack trace.
placeholder: |
Example: When using SSH to connect to my remote Linux machine from my local macOS device in Ghostty, I try to run `clear`, and the screen does not clear. Instead, I see the following error message printed to the terminal: `Error opening terminal: xterm-ghostty.`
When using SSH to connect to my remote Linux machine from my local macOS device in Ghostty, I try to run `clear`, and the screen does not clear. Instead, I see the following error message printed to the terminal: `Error opening terminal: xterm-ghostty.`
validations:
required: true
- type: textarea
attributes:
label: Expected Behavior
description: |
Describe how you expect Ghostty to behave in this situation. Include any relevant documentation links.
placeholder: |
The screen is cleared and the prompt is redrawn at the top of the window.
validations:
required: true
- type: textarea
attributes:
label: Actual Behavior
description: |
Describe how Ghostty actually behaves in this situation. If it is not immediately obvious how the actual behavior differs from the expected behavior described above, please be sure to mention the deviation specifically.
placeholder: |
The screen is not cleared, and an error is printed: `Error opening terminal: xterm-ghostty`.
validations:
required: true
- type: textarea
@ -44,6 +59,12 @@ body:
4. Observe `xterm-ghostty` error message above.
validations:
required: true
- type: textarea
attributes:
label: Ghostty Logs
description: |
Provide any captured Ghostty logs or stacktraces during your issue reproduction in this field. On Linux, logs can be found by running `ghostty` from the command-line; on macOS, logs can be viewed with `sudo log stream --level debug --predicate 'subsystem=="com.mitchellh.ghostty"'` from another terminal emulator.
render: text
- type: textarea
attributes:
label: Ghostty Version
@ -93,9 +114,9 @@ body:
required: false
- type: textarea
attributes:
label: Ghostty Configuration
label: Minimal Ghostty Configuration
description: |
Please provide the minimum configuration needed to reproduce this issue. If you cannot determine this, paste the output of `ghostty +show-config` here.
Please provide the **minimum** configuration needed to reproduce this issue. If you can still reproduce the issue with one of the lines removed, do not include that line. If and **only** if you are not able to determine this, paste the contents of your Ghostty configuration file here.
placeholder: |
font-family = CommitMono Nerd Font
font-family-bold = CommitMono Nerd Font
@ -112,15 +133,15 @@ body:
attributes:
label: Additional Relevant Configuration
description: |
If your issue involves other programs, tools, or applications in addition to Ghostty (e.g. Neovim, tmux, Zellij, etc.), please provide the minimum configuration needed for all relevant programs to reproduce the issue here. If you use custom CSS or shaders for Ghostty, also include them here, if applicable to your issue.
If your issue involves other programs, tools, or applications in addition to Ghostty (e.g. Neovim, tmux, Zellij, etc.), please provide the minimum configuration and versions needed for all relevant programs to reproduce the issue here. If you use custom CSS or shaders for Ghostty, also include them here, if applicable to your issue.
placeholder: |
`tmux.conf`
---
#### `tmux.conf` (tmux 3.5a)
```
set -g default-terminal "tmux-256color"
set-option -sa terminal-overrides ",xterm*:Tc"
set -g base-index 1
setw -g pane-base-index 1
render: text
```
validations:
required: false
- type: markdown
@ -137,3 +158,5 @@ body:
required: true
- label: I have searched the Ghostty repository (both open and closed Discussions and Issues) and confirm this is not a duplicate of an existing issue or discussion.
required: true
- label: I have checked the "Preview" tab on all text fields to ensure that everything looks right, and have wrapped all configuration and code in code blocks with a group of three backticks (` ``` `) on separate lines.
required: true

View File

@ -71,6 +71,7 @@ jobs:
build.zig.zon.nix
build.zig.zon.txt
build.zig.zon.json
flatpak/zig-packages.json
body: |
Upstream revision: https://github.com/mbadolato/iTerm2-Color-Schemes/tree/${{ steps.zig_fetch.outputs.upstream_rev }}
labels: dependencies

View File

@ -103,8 +103,8 @@
// Other
.apple_sdk = .{ .path = "./pkg/apple-sdk" },
.iterm2_themes = .{
.url = "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/5233095e442645995e8af1fcb7b011478ee86f32.tar.gz",
.hash = "N-V-__8AAA38OASk6VOHVXwuyGVAeYu0nghqa1RSIliXV5ym",
.url = "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/1e4957e65005908993250f8f07be3f70e805195e.tar.gz",
.hash = "N-V-__8AAHOASAQuLADcCSHLHEJiKFVLZiCD9Aq2rh5GT01A",
.lazy = true,
},
},

6
build.zig.zon.json generated
View File

@ -54,10 +54,10 @@
"url": "https://deps.files.ghostty.org/imgui-1220bc6b9daceaf7c8c60f3c3998058045ba0c5c5f48ae255ff97776d9cd8bfc6402.tar.gz",
"hash": "sha256-oF/QHgTPEat4Hig4fGIdLkIPHmBEyOJ6JeYD6pnveGA="
},
"N-V-__8AAA38OASk6VOHVXwuyGVAeYu0nghqa1RSIliXV5ym": {
"N-V-__8AAHOASAQuLADcCSHLHEJiKFVLZiCD9Aq2rh5GT01A": {
"name": "iterm2_themes",
"url": "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/5233095e442645995e8af1fcb7b011478ee86f32.tar.gz",
"hash": "sha256-Vy5muiJ3hJXcOvmFHLhqc+Dvdh74GG6+u/L+EsavDb0="
"url": "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/1e4957e65005908993250f8f07be3f70e805195e.tar.gz",
"hash": "sha256-xpDitXpZrdU/EcgLyG4G0cEiT4r42viy+DJALmy2sQE="
},
"N-V-__8AAJrvXQCqAT8Mg9o_tk6m0yf5Fz-gCNEOKLyTSerD": {
"name": "libpng",

6
build.zig.zon.nix generated
View File

@ -170,11 +170,11 @@ in
};
}
{
name = "N-V-__8AAA38OASk6VOHVXwuyGVAeYu0nghqa1RSIliXV5ym";
name = "N-V-__8AAHOASAQuLADcCSHLHEJiKFVLZiCD9Aq2rh5GT01A";
path = fetchZigArtifact {
name = "iterm2_themes";
url = "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/5233095e442645995e8af1fcb7b011478ee86f32.tar.gz";
hash = "sha256-Vy5muiJ3hJXcOvmFHLhqc+Dvdh74GG6+u/L+EsavDb0=";
url = "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/1e4957e65005908993250f8f07be3f70e805195e.tar.gz";
hash = "sha256-xpDitXpZrdU/EcgLyG4G0cEiT4r42viy+DJALmy2sQE=";
};
}
{

2
build.zig.zon.txt generated
View File

@ -27,7 +27,7 @@ https://deps.files.ghostty.org/ziglyph-b89d43d1e3fb01b6074bc1f7fc980324b04d26a5.
https://deps.files.ghostty.org/zlib-1220fed0c74e1019b3ee29edae2051788b080cd96e90d56836eea857b0b966742efb.tar.gz
https://github.com/glfw/glfw/archive/e7ea71be039836da3a98cea55ae5569cb5eb885c.tar.gz
https://github.com/jcollie/ghostty-gobject/releases/download/0.14.0-2025-03-18-21-1/ghostty-gobject-0.14.0-2025-03-18-21-1.tar.zst
https://github.com/mbadolato/iTerm2-Color-Schemes/archive/5233095e442645995e8af1fcb7b011478ee86f32.tar.gz
https://github.com/mbadolato/iTerm2-Color-Schemes/archive/1e4957e65005908993250f8f07be3f70e805195e.tar.gz
https://github.com/mitchellh/libxev/archive/3df9337a9e84450a58a2c4af434ec1a036f7b494.tar.gz
https://github.com/mitchellh/zig-objc/archive/3ab0d37c7d6b933d6ded1b3a35b6b60f05590a98.tar.gz
https://github.com/natecraddock/zf/archive/7aacbe6d155d64d15937ca95ca6c014905eb531f.tar.gz

View File

@ -67,9 +67,9 @@
},
{
"type": "archive",
"url": "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/5233095e442645995e8af1fcb7b011478ee86f32.tar.gz",
"dest": "vendor/p/N-V-__8AAA38OASk6VOHVXwuyGVAeYu0nghqa1RSIliXV5ym",
"sha256": "572e66ba22778495dc3af9851cb86a73e0ef761ef8186ebebbf2fe12c6af0dbd"
"url": "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/1e4957e65005908993250f8f07be3f70e805195e.tar.gz",
"dest": "vendor/p/N-V-__8AAHOASAQuLADcCSHLHEJiKFVLZiCD9Aq2rh5GT01A",
"sha256": "c690e2b57a59add53f11c80bc86e06d1c1224f8af8daf8b2f832402e6cb6b101"
},
{
"type": "archive",

View File

@ -240,6 +240,9 @@ typedef enum {
GHOSTTY_KEY_KP_DELETE,
GHOSTTY_KEY_KP_BEGIN,
// special keys
GHOSTTY_KEY_CONTEXT_MENU,
// modifiers
GHOSTTY_KEY_LEFT_SHIFT,
GHOSTTY_KEY_LEFT_CONTROL,

View File

@ -29,7 +29,6 @@ struct TerminalCommandPaletteView: View {
let key = String(cString: c.action_key)
switch (key) {
case "toggle_tab_overview",
"toggle_maximize",
"toggle_window_decorations":
return false
default:

View File

@ -1,5 +1,6 @@
import Cocoa
import SwiftUI
import Combine
import GhosttyKit
/// A base class for windows that can contain Ghostty windows. This base class implements
@ -71,6 +72,9 @@ class BaseTerminalController: NSWindowController,
/// The configuration derived from the Ghostty config so we don't need to rely on references.
private var derivedConfig: DerivedConfig
/// The cancellables related to our focused surface.
private var focusedSurfaceCancellables: Set<AnyCancellable> = []
struct SavedFrame {
let window: NSRect
let screen: NSRect
@ -115,6 +119,11 @@ class BaseTerminalController: NSWindowController,
selector: #selector(ghosttyCommandPaletteDidToggle(_:)),
name: .ghosttyCommandPaletteDidToggle,
object: nil)
center.addObserver(
self,
selector: #selector(ghosttyMaximizeDidToggle(_:)),
name: .ghosttyMaximizeDidToggle,
object: nil)
// Listen for local events that we need to know of outside of
// single surface handlers.
@ -234,6 +243,13 @@ class BaseTerminalController: NSWindowController,
toggleCommandPalette(nil)
}
@objc private func ghosttyMaximizeDidToggle(_ notification: Notification) {
guard let window else { return }
guard let surfaceView = notification.object as? Ghostty.SurfaceView else { return }
guard surfaceTree?.contains(view: surfaceView) ?? false else { return }
window.zoom(nil)
}
// MARK: Local Events
private func localEventHandler(_ event: NSEvent) -> NSEvent? {
@ -274,7 +290,26 @@ class BaseTerminalController: NSWindowController,
func surfaceTreeDidChange() {}
func focusedSurfaceDidChange(to: Ghostty.SurfaceView?) {
let lastFocusedSurface = focusedSurface
focusedSurface = to
// Important to cancel any prior subscriptions
focusedSurfaceCancellables = []
// Setup our title listener. If we have a focused surface we always use that.
// Otherwise, we try to use our last focused surface. In either case, we only
// want to care if the surface is in the tree so we don't listen to titles of
// closed surfaces.
if let titleSurface = focusedSurface ?? lastFocusedSurface,
surfaceTree?.contains(view: titleSurface) ?? false {
// If we have a surface, we want to listen for title changes.
titleSurface.$title
.sink { [weak self] in self?.titleDidChange(to: $0) }
.store(in: &focusedSurfaceCancellables)
} else {
// There is no surface to listen to titles for.
titleDidChange(to: "👻")
}
}
func titleDidChange(to: String) {
@ -361,14 +396,6 @@ class BaseTerminalController: NSWindowController,
}
}
func fullscreenDidChange() {
// For some reason focus can get lost when we change fullscreen. Regardless of
// mode above we just move it back.
if let focusedSurface {
Ghostty.moveFocus(to: focusedSurface)
}
}
// MARK: Clipboard Confirmation
@objc private func onConfirmClipboardRequest(notification: SwiftUI.Notification) {

View File

@ -121,9 +121,7 @@ class TerminalController: BaseTerminalController {
}
override func fullscreenDidChange() {
super.fullscreenDidChange()
func fullscreenDidChange() {
// When our fullscreen state changes, we resync our appearance because some
// properties change when fullscreen or not.
guard let focusedSurface else { return }

View File

@ -8,9 +8,6 @@ protocol TerminalViewDelegate: AnyObject {
/// Called when the currently focused surface changed. This can be nil.
func focusedSurfaceDidChange(to: Ghostty.SurfaceView?)
/// The title of the terminal should change.
func titleDidChange(to: String)
/// The URL of the pwd should change.
func pwdDidChange(to: URL?)
@ -59,19 +56,10 @@ struct TerminalView<ViewModel: TerminalViewModel>: View {
// Various state values sent back up from the currently focused terminals.
@FocusedValue(\.ghosttySurfaceView) private var focusedSurface
@FocusedValue(\.ghosttySurfaceTitle) private var surfaceTitle
@FocusedValue(\.ghosttySurfacePwd) private var surfacePwd
@FocusedValue(\.ghosttySurfaceZoomed) private var zoomedSplit
@FocusedValue(\.ghosttySurfaceCellSize) private var cellSize
// The title for our window
private var title: String {
if let surfaceTitle, !surfaceTitle.isEmpty {
return surfaceTitle
}
return "👻"
}
// The pwd of the focused surface as a URL
private var pwdURL: URL? {
guard let surfacePwd, surfacePwd != "" else { return nil }
@ -105,9 +93,6 @@ struct TerminalView<ViewModel: TerminalViewModel>: View {
self.delegate?.focusedSurfaceDidChange(to: newValue)
}
}
.onChange(of: title) { newValue in
self.delegate?.titleDidChange(to: newValue)
}
.onChange(of: pwdURL) { newValue in
self.delegate?.pwdDidChange(to: newValue)
}

View File

@ -526,6 +526,9 @@ extension Ghostty {
case GHOSTTY_ACTION_TOGGLE_COMMAND_PALETTE:
toggleCommandPalette(app, target: target)
case GHOSTTY_ACTION_TOGGLE_MAXIMIZE:
toggleMaximize(app, target: target)
case GHOSTTY_ACTION_TOGGLE_QUICK_TERMINAL:
toggleQuickTerminal(app, target: target)
@ -770,6 +773,29 @@ extension Ghostty {
}
}
private static func toggleMaximize(
_ app: ghostty_app_t,
target: ghostty_target_s
) {
switch (target.tag) {
case GHOSTTY_TARGET_APP:
Ghostty.logger.warning("toggle maximize does nothing with an app target")
return
case GHOSTTY_TARGET_SURFACE:
guard let surface = target.target.surface else { return }
guard let surfaceView = self.surfaceView(from: surface) else { return }
NotificationCenter.default.post(
name: .ghosttyMaximizeDidToggle,
object: surfaceView
)
default:
assertionFailure()
}
}
private static func toggleVisibility(
_ app: ghostty_app_t,
target: ghostty_target_s

View File

@ -45,8 +45,6 @@ extension Ghostty {
/// this one.
@Binding var zoomedSurface: SurfaceView?
@FocusedValue(\.ghosttySurfaceTitle) private var surfaceTitle: String?
var body: some View {
let center = NotificationCenter.default
let pubZoom = center.publisher(for: Notification.didToggleSplitZoom)
@ -77,7 +75,6 @@ extension Ghostty {
.onReceive(pubZoom) { onZoom(notification: $0) }
}
}
.navigationTitle(surfaceTitle ?? "Ghostty")
.id(node) // Needed for change detection on node
} else {
// On these events we want to reset the split state and call it.

View File

@ -31,7 +31,6 @@ extension Ghostty {
}, right: {
InspectorViewRepresentable(surfaceView: surfaceView)
.focused($inspectorFocus)
.focusedValue(\.ghosttySurfaceTitle, surfaceView.title)
.focusedValue(\.ghosttySurfaceView, surfaceView)
})
}

View File

@ -279,6 +279,9 @@ extension Notification.Name {
/// Ring the bell
static let ghosttyBellDidRing = Notification.Name("com.mitchellh.ghostty.ghosttyBellDidRing")
static let ghosttyCommandPaletteDidToggle = Notification.Name("com.mitchellh.ghostty.commandPaletteDidToggle")
/// Toggle maximize of current window
static let ghosttyMaximizeDidToggle = Notification.Name("com.mitchellh.ghostty.maximizeDidToggle")
}
// NOTE: I am moving all of these to Notification.Name extensions over time. This

View File

@ -6,14 +6,12 @@ extension Ghostty {
/// Render a terminal for the active app in the environment.
struct Terminal: View {
@EnvironmentObject private var ghostty: Ghostty.App
@FocusedValue(\.ghosttySurfaceTitle) private var surfaceTitle: String?
var body: some View {
if let app = self.ghostty.app {
SurfaceForApp(app) { surfaceView in
SurfaceWrapper(surfaceView: surfaceView)
}
.navigationTitle(surfaceTitle ?? "Ghostty")
}
}
}
@ -83,7 +81,6 @@ extension Ghostty {
Surface(view: surfaceView, size: geo.size)
.focused($surfaceFocus)
.focusedValue(\.ghosttySurfaceTitle, title)
.focusedValue(\.ghosttySurfacePwd, surfaceView.pwd)
.focusedValue(\.ghosttySurfaceView, surfaceView)
.focusedValue(\.ghosttySurfaceCellSize, surfaceView.cellSize)
@ -496,15 +493,6 @@ extension FocusedValues {
typealias Value = Ghostty.SurfaceView
}
var ghosttySurfaceTitle: String? {
get { self[FocusedGhosttySurfaceTitle.self] }
set { self[FocusedGhosttySurfaceTitle.self] = newValue }
}
struct FocusedGhosttySurfaceTitle: FocusedValueKey {
typealias Value = String
}
var ghosttySurfacePwd: String? {
get { self[FocusedGhosttySurfacePwd.self] }
set { self[FocusedGhosttySurfacePwd.self] = newValue }

View File

@ -171,6 +171,13 @@ class NonNativeFullscreen: FullscreenBase, FullscreenStyle {
guard let savedState = SavedState(window) else { return }
self.savedState = savedState
// Get our current first responder on this window. For non-native fullscreen
// we have to restore this because for some reason the operations below
// lose it (see: https://github.com/ghostty-org/ghostty/issues/6999).
// I don't know the root cause here so if we can figure that out there may
// be a nicer way than this.
let firstResponder = window.firstResponder
// We hide the dock if the window is on a screen with the dock.
// We must hide the dock FIRST then hide the menu:
// If you specify autoHideMenuBar, it must be accompanied by either hideDock or autoHideDock.
@ -207,6 +214,10 @@ class NonNativeFullscreen: FullscreenBase, FullscreenStyle {
// https://github.com/ghostty-org/ghostty/issues/1996
DispatchQueue.main.async {
self.window.setFrame(self.fullscreenFrame(screen), display: true)
if let firstResponder {
self.window.makeFirstResponder(firstResponder)
}
self.delegate?.fullscreenDidChange()
}
}
@ -220,6 +231,9 @@ class NonNativeFullscreen: FullscreenBase, FullscreenStyle {
let center = NotificationCenter.default
center.removeObserver(self, name: NSWindow.didChangeScreenNotification, object: window)
// See enter where we do the same thing to understand why.
let firstResponder = window.firstResponder
// Unhide our elements
if savedState.dock {
unhideDock()
@ -258,6 +272,10 @@ class NonNativeFullscreen: FullscreenBase, FullscreenStyle {
}
}
if let firstResponder {
window.makeFirstResponder(firstResponder)
}
// Unset our saved state, we're restored!
self.savedState = nil
@ -355,16 +373,23 @@ class NonNativeFullscreen: FullscreenBase, FullscreenStyle {
self.styleMask = window.styleMask
self.dock = window.screen?.hasDock ?? false
// We hide the menu only if this window is not on any fullscreen
// spaces. We do this because fullscreen spaces already hide the
// menu and if we insert/remove this presentation option we get
// issues (see #7075)
let activeSpace = CGSSpace.active()
let spaces = CGSSpace.list(for: window.cgWindowId)
if spaces.contains(activeSpace) {
self.menu = activeSpace.type != .fullscreen
if let cgWindowId = window.cgWindowId {
// We hide the menu only if this window is not on any fullscreen
// spaces. We do this because fullscreen spaces already hide the
// menu and if we insert/remove this presentation option we get
// issues (see #7075)
let activeSpace = CGSSpace.active()
let spaces = CGSSpace.list(for: cgWindowId)
if spaces.contains(activeSpace) {
self.menu = activeSpace.type != .fullscreen
} else {
self.menu = spaces.allSatisfy { $0.type != .fullscreen }
}
} else {
self.menu = spaces.allSatisfy { $0.type != .fullscreen }
// Window doesn't have a window device, its not visible or something.
// In this case, we assume we can hide the menu. We may want to do
// something more sophisticated but this works for now.
self.menu = true
}
}
}

View File

@ -2,7 +2,11 @@ import AppKit
extension NSWindow {
/// Get the CGWindowID type for the window (used for low level CoreGraphics APIs).
var cgWindowId: CGWindowID {
CGWindowID(windowNumber)
var cgWindowId: CGWindowID? {
// "If the window doesnt have a window device, the value of this
// property is equal to or less than 0." - Docs. In practice I've
// found this is true if a window is not visible.
guard windowNumber > 0 else { return nil }
return CGWindowID(windowNumber)
}
}

View File

@ -1,20 +1,20 @@
pub const c = @import("c.zig").c;
// OpenGL
pub extern fn ImGui_ImplOpenGL3_Init(?[*:0]const u8) callconv(.C) bool;
pub extern fn ImGui_ImplOpenGL3_Shutdown() callconv(.C) void;
pub extern fn ImGui_ImplOpenGL3_NewFrame() callconv(.C) void;
pub extern fn ImGui_ImplOpenGL3_RenderDrawData(*c.ImDrawData) callconv(.C) void;
pub extern fn ImGui_ImplOpenGL3_Init(?[*:0]const u8) callconv(.c) bool;
pub extern fn ImGui_ImplOpenGL3_Shutdown() callconv(.c) void;
pub extern fn ImGui_ImplOpenGL3_NewFrame() callconv(.c) void;
pub extern fn ImGui_ImplOpenGL3_RenderDrawData(*c.ImDrawData) callconv(.c) void;
// Metal
pub extern fn ImGui_ImplMetal_Init(*anyopaque) callconv(.C) bool;
pub extern fn ImGui_ImplMetal_Shutdown() callconv(.C) void;
pub extern fn ImGui_ImplMetal_NewFrame(*anyopaque) callconv(.C) void;
pub extern fn ImGui_ImplMetal_RenderDrawData(*c.ImDrawData, *anyopaque, *anyopaque) callconv(.C) void;
pub extern fn ImGui_ImplMetal_Init(*anyopaque) callconv(.c) bool;
pub extern fn ImGui_ImplMetal_Shutdown() callconv(.c) void;
pub extern fn ImGui_ImplMetal_NewFrame(*anyopaque) callconv(.c) void;
pub extern fn ImGui_ImplMetal_RenderDrawData(*c.ImDrawData, *anyopaque, *anyopaque) callconv(.c) void;
// OSX
pub extern fn ImGui_ImplOSX_Init(*anyopaque) callconv(.C) bool;
pub extern fn ImGui_ImplOSX_Shutdown() callconv(.C) void;
pub extern fn ImGui_ImplOSX_NewFrame(*anyopaque) callconv(.C) void;
pub extern fn ImGui_ImplOSX_Init(*anyopaque) callconv(.c) bool;
pub extern fn ImGui_ImplOSX_Shutdown() callconv(.c) void;
pub extern fn ImGui_ImplOSX_NewFrame(*anyopaque) callconv(.c) void;
test {}

View File

@ -333,7 +333,7 @@ pub inline fn setCallback(comptime callback: ?fn (joystick: Joystick, event: Eve
if (callback) |user_callback| {
const CWrapper = struct {
pub fn joystickCallbackWrapper(jid: c_int, event: c_int) callconv(.C) void {
pub fn joystickCallbackWrapper(jid: c_int, event: c_int) callconv(.c) void {
@call(.always_inline, user_callback, .{
Joystick{ .jid = @as(Joystick.Id, @enumFromInt(jid)) },
@as(Event, @enumFromInt(event)),

View File

@ -389,7 +389,7 @@ pub inline fn setCallback(comptime callback: ?fn (monitor: Monitor, event: Event
if (callback) |user_callback| {
const CWrapper = struct {
pub fn monitorCallbackWrapper(monitor: ?*c.GLFWmonitor, event: c_int) callconv(.C) void {
pub fn monitorCallbackWrapper(monitor: ?*c.GLFWmonitor, event: c_int) callconv(.c) void {
@call(.always_inline, user_callback, .{
Monitor{ .handle = monitor.? },
@as(Event, @enumFromInt(event)),

View File

@ -1230,7 +1230,7 @@ pub inline fn setPosCallback(self: Window, comptime callback: ?fn (window: Windo
if (callback) |user_callback| {
const CWrapper = struct {
pub fn posCallbackWrapper(handle: ?*c.GLFWwindow, xpos: c_int, ypos: c_int) callconv(.C) void {
pub fn posCallbackWrapper(handle: ?*c.GLFWwindow, xpos: c_int, ypos: c_int) callconv(.c) void {
@call(.always_inline, user_callback, .{
from(handle.?),
@as(i32, @intCast(xpos)),
@ -1263,7 +1263,7 @@ pub inline fn setSizeCallback(self: Window, comptime callback: ?fn (window: Wind
if (callback) |user_callback| {
const CWrapper = struct {
pub fn sizeCallbackWrapper(handle: ?*c.GLFWwindow, width: c_int, height: c_int) callconv(.C) void {
pub fn sizeCallbackWrapper(handle: ?*c.GLFWwindow, width: c_int, height: c_int) callconv(.c) void {
@call(.always_inline, user_callback, .{
from(handle.?),
@as(i32, @intCast(width)),
@ -1304,7 +1304,7 @@ pub inline fn setCloseCallback(self: Window, comptime callback: ?fn (window: Win
if (callback) |user_callback| {
const CWrapper = struct {
pub fn closeCallbackWrapper(handle: ?*c.GLFWwindow) callconv(.C) void {
pub fn closeCallbackWrapper(handle: ?*c.GLFWwindow) callconv(.c) void {
@call(.always_inline, user_callback, .{
from(handle.?),
});
@ -1341,7 +1341,7 @@ pub inline fn setRefreshCallback(self: Window, comptime callback: ?fn (window: W
if (callback) |user_callback| {
const CWrapper = struct {
pub fn refreshCallbackWrapper(handle: ?*c.GLFWwindow) callconv(.C) void {
pub fn refreshCallbackWrapper(handle: ?*c.GLFWwindow) callconv(.c) void {
@call(.always_inline, user_callback, .{
from(handle.?),
});
@ -1379,7 +1379,7 @@ pub inline fn setFocusCallback(self: Window, comptime callback: ?fn (window: Win
if (callback) |user_callback| {
const CWrapper = struct {
pub fn focusCallbackWrapper(handle: ?*c.GLFWwindow, focused: c_int) callconv(.C) void {
pub fn focusCallbackWrapper(handle: ?*c.GLFWwindow, focused: c_int) callconv(.c) void {
@call(.always_inline, user_callback, .{
from(handle.?),
focused == c.GLFW_TRUE,
@ -1413,7 +1413,7 @@ pub inline fn setIconifyCallback(self: Window, comptime callback: ?fn (window: W
if (callback) |user_callback| {
const CWrapper = struct {
pub fn iconifyCallbackWrapper(handle: ?*c.GLFWwindow, iconified: c_int) callconv(.C) void {
pub fn iconifyCallbackWrapper(handle: ?*c.GLFWwindow, iconified: c_int) callconv(.c) void {
@call(.always_inline, user_callback, .{
from(handle.?),
iconified == c.GLFW_TRUE,
@ -1448,7 +1448,7 @@ pub inline fn setMaximizeCallback(self: Window, comptime callback: ?fn (window:
if (callback) |user_callback| {
const CWrapper = struct {
pub fn maximizeCallbackWrapper(handle: ?*c.GLFWwindow, maximized: c_int) callconv(.C) void {
pub fn maximizeCallbackWrapper(handle: ?*c.GLFWwindow, maximized: c_int) callconv(.c) void {
@call(.always_inline, user_callback, .{
from(handle.?),
maximized == c.GLFW_TRUE,
@ -1483,7 +1483,7 @@ pub inline fn setFramebufferSizeCallback(self: Window, comptime callback: ?fn (w
if (callback) |user_callback| {
const CWrapper = struct {
pub fn framebufferSizeCallbackWrapper(handle: ?*c.GLFWwindow, width: c_int, height: c_int) callconv(.C) void {
pub fn framebufferSizeCallbackWrapper(handle: ?*c.GLFWwindow, width: c_int, height: c_int) callconv(.c) void {
@call(.always_inline, user_callback, .{
from(handle.?),
@as(u32, @intCast(width)),
@ -1519,7 +1519,7 @@ pub inline fn setContentScaleCallback(self: Window, comptime callback: ?fn (wind
if (callback) |user_callback| {
const CWrapper = struct {
pub fn windowScaleCallbackWrapper(handle: ?*c.GLFWwindow, xscale: f32, yscale: f32) callconv(.C) void {
pub fn windowScaleCallbackWrapper(handle: ?*c.GLFWwindow, xscale: f32, yscale: f32) callconv(.c) void {
@call(.always_inline, user_callback, .{
from(handle.?),
xscale,
@ -1871,7 +1871,7 @@ pub inline fn setKeyCallback(self: Window, comptime callback: ?fn (window: Windo
if (callback) |user_callback| {
const CWrapper = struct {
pub fn keyCallbackWrapper(handle: ?*c.GLFWwindow, key: c_int, scancode: c_int, action: c_int, mods: c_int) callconv(.C) void {
pub fn keyCallbackWrapper(handle: ?*c.GLFWwindow, key: c_int, scancode: c_int, action: c_int, mods: c_int) callconv(.c) void {
@call(.always_inline, user_callback, .{
from(handle.?),
@as(Key, @enumFromInt(key)),
@ -1917,7 +1917,7 @@ pub inline fn setCharCallback(self: Window, comptime callback: ?fn (window: Wind
if (callback) |user_callback| {
const CWrapper = struct {
pub fn charCallbackWrapper(handle: ?*c.GLFWwindow, codepoint: c_uint) callconv(.C) void {
pub fn charCallbackWrapper(handle: ?*c.GLFWwindow, codepoint: c_uint) callconv(.c) void {
@call(.always_inline, user_callback, .{
from(handle.?),
@as(u21, @intCast(codepoint)),
@ -1958,7 +1958,7 @@ pub inline fn setMouseButtonCallback(self: Window, comptime callback: ?fn (windo
if (callback) |user_callback| {
const CWrapper = struct {
pub fn mouseButtonCallbackWrapper(handle: ?*c.GLFWwindow, button: c_int, action: c_int, mods: c_int) callconv(.C) void {
pub fn mouseButtonCallbackWrapper(handle: ?*c.GLFWwindow, button: c_int, action: c_int, mods: c_int) callconv(.c) void {
@call(.always_inline, user_callback, .{
from(handle.?),
@as(MouseButton, @enumFromInt(button)),
@ -1996,7 +1996,7 @@ pub inline fn setCursorPosCallback(self: Window, comptime callback: ?fn (window:
if (callback) |user_callback| {
const CWrapper = struct {
pub fn cursorPosCallbackWrapper(handle: ?*c.GLFWwindow, xpos: f64, ypos: f64) callconv(.C) void {
pub fn cursorPosCallbackWrapper(handle: ?*c.GLFWwindow, xpos: f64, ypos: f64) callconv(.c) void {
@call(.always_inline, user_callback, .{
from(handle.?),
xpos,
@ -2030,7 +2030,7 @@ pub inline fn setCursorEnterCallback(self: Window, comptime callback: ?fn (windo
if (callback) |user_callback| {
const CWrapper = struct {
pub fn cursorEnterCallbackWrapper(handle: ?*c.GLFWwindow, entered: c_int) callconv(.C) void {
pub fn cursorEnterCallbackWrapper(handle: ?*c.GLFWwindow, entered: c_int) callconv(.c) void {
@call(.always_inline, user_callback, .{
from(handle.?),
entered == c.GLFW_TRUE,
@ -2067,7 +2067,7 @@ pub inline fn setScrollCallback(self: Window, comptime callback: ?fn (window: Wi
if (callback) |user_callback| {
const CWrapper = struct {
pub fn scrollCallbackWrapper(handle: ?*c.GLFWwindow, xoffset: f64, yoffset: f64) callconv(.C) void {
pub fn scrollCallbackWrapper(handle: ?*c.GLFWwindow, xoffset: f64, yoffset: f64) callconv(.c) void {
@call(.always_inline, user_callback, .{
from(handle.?),
xoffset,
@ -2110,7 +2110,7 @@ pub inline fn setDropCallback(self: Window, comptime callback: ?fn (window: Wind
if (callback) |user_callback| {
const CWrapper = struct {
pub fn dropCallbackWrapper(handle: ?*c.GLFWwindow, path_count: c_int, paths: [*c][*c]const u8) callconv(.C) void {
pub fn dropCallbackWrapper(handle: ?*c.GLFWwindow, path_count: c_int, paths: [*c][*c]const u8) callconv(.c) void {
@call(.always_inline, user_callback, .{
from(handle.?),
@as([*][*:0]const u8, @ptrCast(paths))[0..@as(u32, @intCast(path_count))],

View File

@ -300,7 +300,7 @@ pub inline fn mustGetErrorString() [:0]const u8 {
pub fn setErrorCallback(comptime callback: ?fn (error_code: ErrorCode, description: [:0]const u8) void) void {
if (callback) |user_callback| {
const CWrapper = struct {
pub fn errorCallbackWrapper(err_int: c_int, c_description: [*c]const u8) callconv(.C) void {
pub fn errorCallbackWrapper(err_int: c_int, c_description: [*c]const u8) callconv(.c) void {
convertError(err_int) catch |error_code| {
user_callback(error_code, mem.sliceTo(c_description, 0));
};

View File

@ -161,7 +161,7 @@ pub const GLProc = *const fn () callconv(if (builtin.os.tag == .windows and buil
/// @thread_safety This function may be called from any thread.
///
/// see also: context_glext, glfwExtensionSupported
pub fn getProcAddress(proc_name: [*:0]const u8) callconv(.C) ?GLProc {
pub fn getProcAddress(proc_name: [*:0]const u8) callconv(.c) ?GLProc {
internal_debug.assertInitialized();
if (c.glfwGetProcAddress(proc_name)) |proc_address| return @ptrCast(proc_address);
return null;

View File

@ -33,7 +33,7 @@ pub fn initVulkanLoader(loader_function: ?VKGetInstanceProcAddr) void {
c.glfwInitVulkanLoader(loader_function orelse null);
}
pub const VKGetInstanceProcAddr = *const fn (vk_instance: c.VkInstance, name: [*c]const u8) callconv(.C) ?VKProc;
pub const VKGetInstanceProcAddr = *const fn (vk_instance: c.VkInstance, name: [*c]const u8) callconv(.c) ?VKProc;
/// Returns whether the Vulkan loader and an ICD have been found.
///
@ -127,7 +127,7 @@ pub const VKProc = *const fn () callconv(if (builtin.os.tag == .windows and buil
/// @pointer_lifetime The returned function pointer is valid until the library is terminated.
///
/// @thread_safety This function may be called from any thread.
pub fn getInstanceProcAddress(vk_instance: ?*anyopaque, proc_name: [*:0]const u8) callconv(.C) ?VKProc {
pub fn getInstanceProcAddress(vk_instance: ?*anyopaque, proc_name: [*:0]const u8) callconv(.c) ?VKProc {
internal_debug.assertInitialized();
if (c.glfwGetInstanceProcAddress(if (vk_instance) |v| @as(c.VkInstance, @ptrCast(v)) else null, proc_name)) |proc_address| return proc_address;
return null;

View File

@ -77,11 +77,11 @@ pub const Blob = struct {
comptime T: type,
key: ?*anyopaque,
ptr: ?*T,
comptime destroycb: ?*const fn (?*T) callconv(.C) void,
comptime destroycb: ?*const fn (?*T) callconv(.c) void,
replace: bool,
) bool {
const Callback = struct {
pub fn callback(data: ?*anyopaque) callconv(.C) void {
pub fn callback(data: ?*anyopaque) callconv(.c) void {
@call(.{ .modifier = .always_inline }, destroycb, .{
@as(?*T, @ptrCast(@alignCast(data))),
});

View File

@ -84,7 +84,7 @@ pub const MutableArray = opaque {
a: *const Elem,
b: *const Elem,
context: ?*Context,
) callconv(.C) ComparisonResult,
) callconv(.c) ComparisonResult,
) void {
CFArraySortValues(
self,
@ -155,7 +155,7 @@ test "array sorting" {
void,
null,
struct {
fn compare(a: *const u8, b: *const u8, _: ?*void) callconv(.C) ComparisonResult {
fn compare(a: *const u8, b: *const u8, _: ?*void) callconv(.c) ComparisonResult {
if (a.* > b.*) return .greater;
if (a.* == b.*) return .equal;
return .less;

View File

@ -66,7 +66,7 @@ pub const DisplayLink = opaque {
flagsIn: c.CVOptionFlags,
flagsOut: *c.CVOptionFlags,
inner_userinfo: ?*anyopaque,
) callconv(.C) c.CVReturn {
) callconv(.c) c.CVReturn {
_ = inNow;
_ = inOutputTime;
_ = flagsIn;

View File

@ -13,8 +13,8 @@ pub threadlocal var context: Context = undefined;
/// The getProcAddress param is an anytype so that we can accept multiple
/// forms of the function depending on what we're interfacing with.
pub fn load(getProcAddress: anytype) !c_int {
const GlProc = *const fn () callconv(.C) void;
const GlfwFn = *const fn ([*:0]const u8) callconv(.C) ?GlProc;
const GlProc = *const fn () callconv(.c) void;
const GlfwFn = *const fn ([*:0]const u8) callconv(.c) ?GlProc;
const res = switch (@TypeOf(getProcAddress)) {
// glfw

View File

@ -5,8 +5,8 @@ const Envelope = @import("envelope.zig").Envelope;
/// sentry_transport_t
pub const Transport = opaque {
pub const SendFunc = *const fn (envelope: *Envelope, state: ?*anyopaque) callconv(.C) void;
pub const FreeFunc = *const fn (state: ?*anyopaque) callconv(.C) void;
pub const SendFunc = *const fn (envelope: *Envelope, state: ?*anyopaque) callconv(.c) void;
pub const FreeFunc = *const fn (state: ?*anyopaque) callconv(.c) void;
pub fn init(f: SendFunc) *Transport {
return @ptrCast(c.sentry_transport_new(@ptrCast(f)).?);

View File

@ -4119,6 +4119,14 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
}, .unlocked);
},
.scroll_to_selection => {
self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock();
const sel = self.io.terminal.screen.selection orelse return false;
const tl = sel.topLeft(&self.io.terminal.screen);
self.io.terminal.screen.scroll(.{ .pin = tl });
},
.scroll_page_up => {
const rows: isize = @intCast(self.size.grid().rows);
self.io.queueMessage(.{

View File

@ -43,15 +43,15 @@ pub const App = struct {
/// Callback called to wakeup the event loop. This should trigger
/// a full tick of the app loop.
wakeup: *const fn (AppUD) callconv(.C) void,
wakeup: *const fn (AppUD) callconv(.c) void,
/// Callback called to handle an action.
action: *const fn (*App, apprt.Target.C, apprt.Action.C) callconv(.C) bool,
action: *const fn (*App, apprt.Target.C, apprt.Action.C) callconv(.c) bool,
/// Read the clipboard value. The return value must be preserved
/// by the host until the next call. If there is no valid clipboard
/// value then this should return null.
read_clipboard: *const fn (SurfaceUD, c_int, *apprt.ClipboardRequest) callconv(.C) void,
read_clipboard: *const fn (SurfaceUD, c_int, *apprt.ClipboardRequest) callconv(.c) void,
/// This may be called after a read clipboard call to request
/// confirmation that the clipboard value is safe to read. The embedder
@ -61,13 +61,13 @@ pub const App = struct {
[*:0]const u8,
*apprt.ClipboardRequest,
apprt.ClipboardRequestType,
) callconv(.C) void,
) callconv(.c) void,
/// Write the clipboard value.
write_clipboard: *const fn (SurfaceUD, [*:0]const u8, c_int, bool) callconv(.C) void,
write_clipboard: *const fn (SurfaceUD, [*:0]const u8, c_int, bool) callconv(.c) void,
/// Close the current surface given by this function.
close_surface: ?*const fn (SurfaceUD, bool) callconv(.C) void = null,
close_surface: ?*const fn (SurfaceUD, bool) callconv(.c) void = null,
};
/// This is the key event sent for ghostty_surface_key and

View File

@ -74,6 +74,9 @@ cursor_none: ?*gdk.Cursor,
/// The clipboard confirmation window, if it is currently open.
clipboard_confirmation_window: ?*ClipboardConfirmationWindow = null,
/// The config errors dialog, if it is currently open.
config_errors_dialog: ?ConfigErrorsDialog = null,
/// The window containing the quick terminal.
/// Null when never initialized.
quick_terminal: ?*Window = null,
@ -159,6 +162,7 @@ pub fn init(core_app: *CoreApp, opts: Options) !App {
opengl: bool = false,
/// disable GLES, Ghostty can't use GLES
@"gl-disable-gles": bool = false,
// GTK's new renderer can cause blurry font when using fractional scaling.
@"gl-no-fractional": bool = false,
/// Disabling Vulkan can improve startup times by hundreds of
/// milliseconds on some systems. We don't use Vulkan so we can just
@ -190,7 +194,6 @@ pub fn init(core_app: *CoreApp, opts: Options) !App {
// For the remainder of "why" see the 4.14 comment below.
gdk_disable.@"gles-api" = true;
gdk_disable.vulkan = true;
gdk_debug.@"gl-no-fractional" = true;
break :environment;
}
if (gtk_version.runtimeAtLeast(4, 14, 0)) {
@ -201,8 +204,12 @@ pub fn init(core_app: *CoreApp, opts: Options) !App {
//
// Upstream issue: https://gitlab.gnome.org/GNOME/gtk/-/issues/6589
gdk_debug.@"gl-disable-gles" = true;
gdk_debug.@"gl-no-fractional" = true;
gdk_debug.@"vulkan-disable" = true;
if (gtk_version.runtimeUntil(4, 17, 5)) {
// Removed at GTK v4.17.5
gdk_debug.@"gl-no-fractional" = true;
}
break :environment;
}
// Versions prior to 4.14 are a bit of an unknown for Ghostty. It
@ -1523,7 +1530,7 @@ fn adwNotifyDark(
style_manager: *adw.StyleManager,
_: *gobject.ParamSpec,
self: *App,
) callconv(.C) void {
) callconv(.c) void {
const color_scheme: apprt.ColorScheme = if (style_manager.getDark() == 0)
.light
else

View File

@ -18,88 +18,37 @@ pub fn init(
/// The minor version of the minimum Adwaita version that is required to use
/// this resource.
comptime minor: u16,
/// `blp` signifies that the resource is a Blueprint that has been compiled
/// to GTK Builder XML at compile time. `ui` signifies that the resource is
/// a GTK Builder XML file that is included in the Ghostty source (perhaps
/// because the Blueprint compiler on some target platforms cannot compile a
/// Blueprint that generates the necessary resources).
comptime kind: enum { blp, ui },
) Builder {
const resource_path = comptime resource_path: {
const gresource = @import("gresource.zig");
switch (kind) {
.blp => {
// Check to make sure that our file is listed as a
// `blueprint_file` in `gresource.zig`. If it isn't Ghostty
// could crash at runtime when we try and load a nonexistent
// GResource.
for (gresource.blueprint_files) |file| {
if (major != file.major or minor != file.minor or !std.mem.eql(u8, file.name, name)) continue;
// Use @embedFile to make sure that the `.blp` file exists
// at compile time. Zig _should_ discard the data so that
// it doesn't end up in the final executable. At runtime we
// will load the data from a GResource.
const blp_filename = std.fmt.comptimePrint(
"ui/{d}.{d}/{s}.blp",
.{
file.major,
file.minor,
file.name,
},
);
_ = @embedFile(blp_filename);
break :resource_path std.fmt.comptimePrint(
"/com/mitchellh/ghostty/ui/{d}.{d}/{s}.ui",
.{
file.major,
file.minor,
file.name,
},
);
} else @compileError("missing blueprint file '" ++ name ++ "' in gresource.zig");
},
.ui => {
// Check to make sure that our file is listed as a `ui_file` in
// `gresource.zig`. If it isn't Ghostty could crash at runtime
// when we try and load a nonexistent GResource.
for (gresource.ui_files) |file| {
if (major != file.major or minor != file.minor or !std.mem.eql(u8, file.name, name)) continue;
// Use @embedFile to make sure that the `.ui` file exists
// at compile time. Zig _should_ discard the data so that
// it doesn't end up in the final executable. At runtime we
// will load the data from a GResource.
const ui_filename = std.fmt.comptimePrint(
"ui/{d}.{d}/{s}.ui",
.{
file.major,
file.minor,
file.name,
},
);
_ = @embedFile(ui_filename);
// Also use @embedFile to make sure that a matching `.blp`
// file exists at compile time. Zig _should_ discard the
// data so that it doesn't end up in the final executable.
const blp_filename = std.fmt.comptimePrint(
"ui/{d}.{d}/{s}.blp",
.{
file.major,
file.minor,
file.name,
},
);
_ = @embedFile(blp_filename);
break :resource_path std.fmt.comptimePrint(
"/com/mitchellh/ghostty/ui/{d}.{d}/{s}.ui",
.{
file.major,
file.minor,
file.name,
},
);
} else @compileError("missing ui file '" ++ name ++ "' in gresource.zig");
},
}
// Check to make sure that our file is listed as a
// `blueprint_file` in `gresource.zig`. If it isn't Ghostty
// could crash at runtime when we try and load a nonexistent
// GResource.
for (gresource.blueprint_files) |file| {
if (major != file.major or minor != file.minor or !std.mem.eql(u8, file.name, name)) continue;
// Use @embedFile to make sure that the `.blp` file exists
// at compile time. Zig _should_ discard the data so that
// it doesn't end up in the final executable. At runtime we
// will load the data from a GResource.
const blp_filename = std.fmt.comptimePrint(
"ui/{d}.{d}/{s}.blp",
.{
file.major,
file.minor,
file.name,
},
);
_ = @embedFile(blp_filename);
break :resource_path std.fmt.comptimePrint(
"/com/mitchellh/ghostty/ui/{d}.{d}/{s}.ui",
.{
file.major,
file.minor,
file.name,
},
);
} else @compileError("missing blueprint file '" ++ name ++ "' in gresource.zig");
};
return .{

View File

@ -71,14 +71,14 @@ fn init(
) !void {
var builder = switch (DialogType) {
adw.AlertDialog => switch (request) {
.osc_52_read => Builder.init("ccw-osc-52-read", 1, 5, .blp),
.osc_52_write => Builder.init("ccw-osc-52-write", 1, 5, .blp),
.paste => Builder.init("ccw-paste", 1, 5, .blp),
.osc_52_read => Builder.init("ccw-osc-52-read", 1, 5),
.osc_52_write => Builder.init("ccw-osc-52-write", 1, 5),
.paste => Builder.init("ccw-paste", 1, 5),
},
adw.MessageDialog => switch (request) {
.osc_52_read => Builder.init("ccw-osc-52-read", 1, 2, .ui),
.osc_52_write => Builder.init("ccw-osc-52-write", 1, 2, .ui),
.paste => Builder.init("ccw-paste", 1, 2, .ui),
.osc_52_read => Builder.init("ccw-osc-52-read", 1, 2),
.osc_52_write => Builder.init("ccw-osc-52-write", 1, 2),
.paste => Builder.init("ccw-paste", 1, 2),
},
else => unreachable,
};
@ -152,7 +152,7 @@ fn init(
}
}
fn gtkResponse(_: *DialogType, response: [*:0]u8, self: *ClipboardConfirmation) callconv(.C) void {
fn gtkResponse(_: *DialogType, response: [*:0]u8, self: *ClipboardConfirmation) callconv(.c) void {
if (std.mem.orderZ(u8, response, "ok") == .eq) {
self.core_surface.completeClipboardRequest(
self.pending_req,
@ -165,7 +165,7 @@ fn gtkResponse(_: *DialogType, response: [*:0]u8, self: *ClipboardConfirmation)
self.destroy();
}
fn gtkRevealButtonClicked(_: *gtk.Button, self: *ClipboardConfirmation) callconv(.C) void {
fn gtkRevealButtonClicked(_: *gtk.Button, self: *ClipboardConfirmation) callconv(.c) void {
self.text_view_scroll.as(gtk.Widget).setSensitive(@intFromBool(true));
self.text_view.as(gtk.Widget).removeCssClass("blurred");
@ -173,7 +173,7 @@ fn gtkRevealButtonClicked(_: *gtk.Button, self: *ClipboardConfirmation) callconv
self.reveal_button.as(gtk.Widget).setVisible(@intFromBool(false));
}
fn gtkHideButtonClicked(_: *gtk.Button, self: *ClipboardConfirmation) callconv(.C) void {
fn gtkHideButtonClicked(_: *gtk.Button, self: *ClipboardConfirmation) callconv(.c) void {
self.text_view_scroll.as(gtk.Widget).setSensitive(@intFromBool(false));
self.text_view.as(gtk.Widget).addCssClass("blurred");

View File

@ -64,7 +64,7 @@ fn responseCallback(
_: *DialogType,
response: [*:0]const u8,
target: *Target,
) callconv(.C) void {
) callconv(.c) void {
const alloc = target.allocator();
defer alloc.destroy(target);
@ -141,7 +141,7 @@ pub const Target = union(enum) {
}
};
fn findActiveWindow(data: ?*const anyopaque, _: ?*const anyopaque) callconv(.C) c_int {
fn findActiveWindow(data: ?*const anyopaque, _: ?*const anyopaque) callconv(.c) c_int {
const window: *gtk.Window = @ptrCast(@alignCast(@constCast(data orelse return -1)));
// Confusingly, `isActive` returns 1 when active,

View File

@ -29,15 +29,38 @@ error_message: *gtk.TextBuffer,
pub fn maybePresent(app: *App, window: ?*Window) void {
if (app.config._diagnostics.empty()) return;
var builder = switch (DialogType) {
adw.AlertDialog => Builder.init("config-errors-dialog", 1, 5, .blp),
adw.MessageDialog => Builder.init("config-errors-dialog", 1, 2, .ui),
else => unreachable,
};
defer builder.deinit();
const config_errors_dialog = config_errors_dialog: {
if (app.config_errors_dialog) |config_errors_dialog| break :config_errors_dialog config_errors_dialog;
const dialog = builder.getObject(DialogType, "config_errors_dialog").?;
const error_message = builder.getObject(gtk.TextBuffer, "error_message").?;
var builder = switch (DialogType) {
adw.AlertDialog => Builder.init("config-errors-dialog", 1, 5),
adw.MessageDialog => Builder.init("config-errors-dialog", 1, 2),
else => unreachable,
};
const dialog = builder.getObject(DialogType, "config_errors_dialog").?;
const error_message = builder.getObject(gtk.TextBuffer, "error_message").?;
_ = DialogType.signals.response.connect(dialog, *App, onResponse, app, .{});
app.config_errors_dialog = .{
.builder = builder,
.dialog = dialog,
.error_message = error_message,
};
break :config_errors_dialog app.config_errors_dialog.?;
};
{
var start = std.mem.zeroes(gtk.TextIter);
config_errors_dialog.error_message.getStartIter(&start);
var end = std.mem.zeroes(gtk.TextIter);
config_errors_dialog.error_message.getEndIter(&end);
config_errors_dialog.error_message.delete(&start, &end);
}
var msg_buf: [4095:0]u8 = undefined;
var fbs = std.io.fixedBufferStream(&msg_buf);
@ -52,22 +75,24 @@ pub fn maybePresent(app: *App, window: ?*Window) void {
continue;
};
error_message.insertAtCursor(&msg_buf, @intCast(fbs.pos));
error_message.insertAtCursor("\n", 1);
config_errors_dialog.error_message.insertAtCursor(&msg_buf, @intCast(fbs.pos));
config_errors_dialog.error_message.insertAtCursor("\n", 1);
}
_ = DialogType.signals.response.connect(dialog, *App, onResponse, app, .{});
const parent = if (window) |w| w.window.as(gtk.Widget) else null;
switch (DialogType) {
adw.AlertDialog => dialog.as(adw.Dialog).present(parent),
adw.MessageDialog => dialog.as(gtk.Window).present(),
adw.AlertDialog => {
const parent = if (window) |w| w.window.as(gtk.Widget) else null;
config_errors_dialog.dialog.as(adw.Dialog).present(parent);
},
adw.MessageDialog => config_errors_dialog.dialog.as(gtk.Window).present(),
else => unreachable,
}
}
fn onResponse(_: *DialogType, response: [*:0]const u8, app: *App) callconv(.C) void {
fn onResponse(_: *DialogType, response: [*:0]const u8, app: *App) callconv(.c) void {
if (app.config_errors_dialog) |config_errors_dialog| config_errors_dialog.builder.deinit();
app.config_errors_dialog = null;
if (std.mem.orderZ(u8, response, "reload") == .eq) {
app.reloadConfig(.app, .{}) catch |err| {
log.warn("error reloading config error={}", .{err});

View File

@ -221,12 +221,12 @@ fn translateMouseButton(button: c_uint) ?c_int {
};
}
fn gtkDestroy(_: *gtk.GLArea, self: *ImguiWidget) callconv(.C) void {
fn gtkDestroy(_: *gtk.GLArea, self: *ImguiWidget) callconv(.c) void {
log.debug("imgui widget destroy", .{});
self.deinit();
}
fn gtkRealize(area: *gtk.GLArea, self: *ImguiWidget) callconv(.C) void {
fn gtkRealize(area: *gtk.GLArea, self: *ImguiWidget) callconv(.c) void {
log.debug("gl surface realized", .{});
// We need to make the context current so we can call GL functions.
@ -242,7 +242,7 @@ fn gtkRealize(area: *gtk.GLArea, self: *ImguiWidget) callconv(.C) void {
_ = cimgui.ImGui_ImplOpenGL3_Init(null);
}
fn gtkUnrealize(area: *gtk.GLArea, self: *ImguiWidget) callconv(.C) void {
fn gtkUnrealize(area: *gtk.GLArea, self: *ImguiWidget) callconv(.c) void {
_ = area;
log.debug("gl surface unrealized", .{});
@ -250,7 +250,7 @@ fn gtkUnrealize(area: *gtk.GLArea, self: *ImguiWidget) callconv(.C) void {
cimgui.ImGui_ImplOpenGL3_Shutdown();
}
fn gtkResize(area: *gtk.GLArea, width: c_int, height: c_int, self: *ImguiWidget) callconv(.C) void {
fn gtkResize(area: *gtk.GLArea, width: c_int, height: c_int, self: *ImguiWidget) callconv(.c) void {
cimgui.c.igSetCurrentContext(self.ig_ctx);
const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
const scale_factor = area.as(gtk.Widget).getScaleFactor();
@ -273,7 +273,7 @@ fn gtkResize(area: *gtk.GLArea, width: c_int, height: c_int, self: *ImguiWidget)
active_style.* = style.*;
}
fn gtkRender(_: *gtk.GLArea, _: *gdk.GLContext, self: *ImguiWidget) callconv(.C) c_int {
fn gtkRender(_: *gtk.GLArea, _: *gdk.GLContext, self: *ImguiWidget) callconv(.c) c_int {
cimgui.c.igSetCurrentContext(self.ig_ctx);
// Setup our frame. We render twice because some ImGui behaviors
@ -307,7 +307,7 @@ fn gtkMouseMotion(
x: f64,
y: f64,
self: *ImguiWidget,
) callconv(.C) void {
) callconv(.c) void {
cimgui.c.igSetCurrentContext(self.ig_ctx);
const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
const scale_factor: f64 = @floatFromInt(self.gl_area.as(gtk.Widget).getScaleFactor());
@ -325,7 +325,7 @@ fn gtkMouseDown(
_: f64,
_: f64,
self: *ImguiWidget,
) callconv(.C) void {
) callconv(.c) void {
self.queueRender();
cimgui.c.igSetCurrentContext(self.ig_ctx);
@ -343,7 +343,7 @@ fn gtkMouseUp(
_: f64,
_: f64,
self: *ImguiWidget,
) callconv(.C) void {
) callconv(.c) void {
self.queueRender();
cimgui.c.igSetCurrentContext(self.ig_ctx);
@ -359,7 +359,7 @@ fn gtkMouseScroll(
x: f64,
y: f64,
self: *ImguiWidget,
) callconv(.C) c_int {
) callconv(.c) c_int {
self.queueRender();
cimgui.c.igSetCurrentContext(self.ig_ctx);
@ -373,7 +373,7 @@ fn gtkMouseScroll(
return @intFromBool(true);
}
fn gtkFocusEnter(_: *gtk.EventControllerFocus, self: *ImguiWidget) callconv(.C) void {
fn gtkFocusEnter(_: *gtk.EventControllerFocus, self: *ImguiWidget) callconv(.c) void {
self.queueRender();
cimgui.c.igSetCurrentContext(self.ig_ctx);
@ -381,7 +381,7 @@ fn gtkFocusEnter(_: *gtk.EventControllerFocus, self: *ImguiWidget) callconv(.C)
cimgui.c.ImGuiIO_AddFocusEvent(io, true);
}
fn gtkFocusLeave(_: *gtk.EventControllerFocus, self: *ImguiWidget) callconv(.C) void {
fn gtkFocusLeave(_: *gtk.EventControllerFocus, self: *ImguiWidget) callconv(.c) void {
self.queueRender();
cimgui.c.igSetCurrentContext(self.ig_ctx);
@ -393,7 +393,7 @@ fn gtkInputCommit(
_: *gtk.IMMulticontext,
bytes: [*:0]u8,
self: *ImguiWidget,
) callconv(.C) void {
) callconv(.c) void {
self.queueRender();
cimgui.c.igSetCurrentContext(self.ig_ctx);
@ -407,7 +407,7 @@ fn gtkKeyPressed(
keycode: c_uint,
gtk_mods: gdk.ModifierType,
self: *ImguiWidget,
) callconv(.C) c_int {
) callconv(.c) c_int {
return @intFromBool(self.keyEvent(
.press,
ec_key,
@ -423,7 +423,7 @@ fn gtkKeyReleased(
keycode: c_uint,
gtk_mods: gdk.ModifierType,
self: *ImguiWidget,
) callconv(.C) void {
) callconv(.c) void {
_ = self.keyEvent(
.release,
ec_key,

View File

@ -104,7 +104,7 @@ pub fn maybeShow(self: *ResizeOverlay) void {
/// Actually update the overlay widget. This should only be called from a GTK
/// idle handler.
fn gtkUpdate(ud: ?*anyopaque) callconv(.C) c_int {
fn gtkUpdate(ud: ?*anyopaque) callconv(.c) c_int {
const self: *ResizeOverlay = @ptrCast(@alignCast(ud orelse return 0));
// No matter what our idler is complete with this callback
@ -198,7 +198,7 @@ fn setPosition(label: *gtk.Label, config: *DerivedConfig) void {
/// If this fires, it means that the delay period has expired and the resize
/// overlay widget should be hidden.
fn gtkTimerExpired(ud: ?*anyopaque) callconv(.C) c_int {
fn gtkTimerExpired(ud: ?*anyopaque) callconv(.c) c_int {
const self: *ResizeOverlay = @ptrCast(@alignCast(ud orelse return 0));
self.timer = null;
if (self.label) |label| hide(label);

View File

@ -1025,7 +1025,7 @@ pub fn setTitle(self: *Surface, slice: [:0]const u8, source: SetTitleSource) !vo
self.update_title_timer = glib.timeoutAdd(75, updateTitleTimerExpired, self);
}
fn updateTitleTimerExpired(ud: ?*anyopaque) callconv(.C) c_int {
fn updateTitleTimerExpired(ud: ?*anyopaque) callconv(.c) c_int {
const self: *Surface = @ptrCast(@alignCast(ud.?));
self.updateTitleLabels();
@ -1061,7 +1061,7 @@ pub fn promptTitle(self: *Surface) !void {
if (!adw_version.atLeast(1, 5, 0)) return;
const window = self.container.window() orelse return;
var builder = Builder.init("prompt-title-dialog", 1, 5, .blp);
var builder = Builder.init("prompt-title-dialog", 1, 5);
defer builder.deinit();
const entry = builder.getObject(gtk.Entry, "title_entry").?;
@ -1265,7 +1265,7 @@ fn gtkClipboardRead(
source: ?*gobject.Object,
res: *gio.AsyncResult,
ud: ?*anyopaque,
) callconv(.C) void {
) callconv(.c) void {
const clipboard = gobject.ext.cast(gdk.Clipboard, source orelse return) orelse return;
const req: *ClipboardRequest = @ptrCast(@alignCast(ud orelse return));
const self = req.self;
@ -1349,7 +1349,7 @@ pub fn showDesktopNotification(
app.sendNotification(body.ptr, notification);
}
fn gtkRealize(gl_area: *gtk.GLArea, self: *Surface) callconv(.C) void {
fn gtkRealize(gl_area: *gtk.GLArea, self: *Surface) callconv(.c) void {
log.debug("gl surface realized", .{});
// We need to make the context current so we can call GL functions.
@ -1377,7 +1377,7 @@ fn gtkRealize(gl_area: *gtk.GLArea, self: *Surface) callconv(.C) void {
/// This is called when the underlying OpenGL resources must be released.
/// This is usually due to the OpenGL area changing GDK surfaces.
fn gtkUnrealize(gl_area: *gtk.GLArea, self: *Surface) callconv(.C) void {
fn gtkUnrealize(gl_area: *gtk.GLArea, self: *Surface) callconv(.c) void {
log.debug("gl surface unrealized", .{});
// See gtkRealize for why we do this here.
@ -1405,7 +1405,7 @@ fn gtkUnrealize(gl_area: *gtk.GLArea, self: *Surface) callconv(.C) void {
}
/// render signal
fn gtkRender(_: *gtk.GLArea, _: *gdk.GLContext, self: *Surface) callconv(.C) c_int {
fn gtkRender(_: *gtk.GLArea, _: *gdk.GLContext, self: *Surface) callconv(.c) c_int {
self.render() catch |err| {
log.err("surface failed to render: {}", .{err});
return 0;
@ -1415,7 +1415,7 @@ fn gtkRender(_: *gtk.GLArea, _: *gdk.GLContext, self: *Surface) callconv(.C) c_i
}
/// resize signal
fn gtkResize(gl_area: *gtk.GLArea, width: c_int, height: c_int, self: *Surface) callconv(.C) void {
fn gtkResize(gl_area: *gtk.GLArea, width: c_int, height: c_int, self: *Surface) callconv(.c) void {
// Some debug output to help understand what GTK is telling us.
{
const scale_factor = scale: {
@ -1471,7 +1471,7 @@ fn gtkResize(gl_area: *gtk.GLArea, width: c_int, height: c_int, self: *Surface)
}
/// "destroy" signal for surface
fn gtkDestroy(_: *gtk.GLArea, self: *Surface) callconv(.C) void {
fn gtkDestroy(_: *gtk.GLArea, self: *Surface) callconv(.c) void {
log.debug("gl destroy", .{});
const alloc = self.app.core_app.alloc;
@ -1505,7 +1505,7 @@ fn gtkMouseDown(
x: f64,
y: f64,
self: *Surface,
) callconv(.C) void {
) callconv(.c) void {
const event = gesture.as(gtk.EventController).getCurrentEvent() orelse return;
const gtk_mods = event.getModifierState();
@ -1538,7 +1538,7 @@ fn gtkMouseUp(
_: f64,
_: f64,
self: *Surface,
) callconv(.C) void {
) callconv(.c) void {
const event = gesture.as(gtk.EventController).getCurrentEvent() orelse return;
const gtk_mods = event.getModifierState();
@ -1557,7 +1557,7 @@ fn gtkMouseMotion(
x: f64,
y: f64,
self: *Surface,
) callconv(.C) void {
) callconv(.c) void {
const event = ec.as(gtk.EventController).getCurrentEvent() orelse return;
const scaled = self.scaledCoordinates(x, y);
@ -1603,7 +1603,7 @@ fn gtkMouseMotion(
fn gtkMouseLeave(
ec_motion: *gtk.EventControllerMotion,
self: *Surface,
) callconv(.C) void {
) callconv(.c) void {
const event = ec_motion.as(gtk.EventController).getCurrentEvent() orelse return;
// Get our modifiers
@ -1618,14 +1618,14 @@ fn gtkMouseLeave(
fn gtkMouseScrollPrecisionBegin(
_: *gtk.EventControllerScroll,
self: *Surface,
) callconv(.C) void {
) callconv(.c) void {
self.precision_scroll = true;
}
fn gtkMouseScrollPrecisionEnd(
_: *gtk.EventControllerScroll,
self: *Surface,
) callconv(.C) void {
) callconv(.c) void {
self.precision_scroll = false;
}
@ -1634,7 +1634,7 @@ fn gtkMouseScroll(
x: f64,
y: f64,
self: *Surface,
) callconv(.C) c_int {
) callconv(.c) c_int {
const scaled = self.scaledCoordinates(x, y);
// GTK doesn't support any of the scroll mods.
@ -1664,7 +1664,7 @@ fn gtkKeyPressed(
keycode: c_uint,
gtk_mods: gdk.ModifierType,
self: *Surface,
) callconv(.C) c_int {
) callconv(.c) c_int {
return @intFromBool(self.keyEvent(
.press,
ec_key,
@ -1680,7 +1680,7 @@ fn gtkKeyReleased(
keycode: c_uint,
state: gdk.ModifierType,
self: *Surface,
) callconv(.C) void {
) callconv(.c) void {
_ = self.keyEvent(
.release,
ec_key,
@ -1971,7 +1971,7 @@ pub fn keyEvent(
fn gtkInputPreeditStart(
_: *gtk.IMMulticontext,
self: *Surface,
) callconv(.C) void {
) callconv(.c) void {
// log.warn("GTKIM: preedit start", .{});
// Start our composing state for the input method and reset our
@ -1983,7 +1983,7 @@ fn gtkInputPreeditStart(
fn gtkInputPreeditChanged(
ctx: *gtk.IMMulticontext,
self: *Surface,
) callconv(.C) void {
) callconv(.c) void {
// Any preedit change should mark that we're composing. Its possible this
// is false using fcitx5-hangul and typing "dkssud<space>" ("안녕"). The
// second "s" results in a "commit" for "" which sets composing to false,
@ -2009,7 +2009,7 @@ fn gtkInputPreeditChanged(
fn gtkInputPreeditEnd(
_: *gtk.IMMulticontext,
self: *Surface,
) callconv(.C) void {
) callconv(.c) void {
// log.warn("GTKIM: preedit end", .{});
// End our composing state for GTK, allowing us to commit the text.
@ -2025,7 +2025,7 @@ fn gtkInputCommit(
_: *gtk.IMMulticontext,
bytes: [*:0]u8,
self: *Surface,
) callconv(.C) void {
) callconv(.c) void {
const str = std.mem.sliceTo(bytes, 0);
// log.debug("GTKIM: input commit composing={} keyevent={} str={s}", .{
@ -2100,7 +2100,7 @@ fn gtkInputCommit(
};
}
fn gtkFocusEnter(_: *gtk.EventControllerFocus, self: *Surface) callconv(.C) void {
fn gtkFocusEnter(_: *gtk.EventControllerFocus, self: *Surface) callconv(.c) void {
if (!self.realized) return;
// Notify our IM context
@ -2125,7 +2125,7 @@ fn gtkFocusEnter(_: *gtk.EventControllerFocus, self: *Surface) callconv(.C) void
};
}
fn gtkFocusLeave(_: *gtk.EventControllerFocus, self: *Surface) callconv(.C) void {
fn gtkFocusLeave(_: *gtk.EventControllerFocus, self: *Surface) callconv(.c) void {
if (!self.realized) return;
// Notify our IM context
@ -2243,7 +2243,7 @@ fn gtkDrop(
_: f64,
_: f64,
self: *Surface,
) callconv(.C) c_int {
) callconv(.c) c_int {
const alloc = self.app.core_app.alloc;
if (g_value_holds(value, gdk.FileList.getGObjectType())) {
@ -2395,7 +2395,7 @@ fn g_value_holds(value_: ?*gobject.Value, g_type: gobject.Type) bool {
return false;
}
fn gtkPromptTitleResponse(source_object: ?*gobject.Object, result: *gio.AsyncResult, ud: ?*anyopaque) callconv(.C) void {
fn gtkPromptTitleResponse(source_object: ?*gobject.Object, result: *gio.AsyncResult, ud: ?*anyopaque) callconv(.c) void {
if (!adw_version.supportsDialogs()) return;
const dialog = gobject.ext.cast(adw.AlertDialog, source_object.?).?;
const self: *Surface = @ptrCast(@alignCast(ud));

View File

@ -161,7 +161,7 @@ pub fn closeWithConfirmation(tab: *Tab) void {
}
}
fn gtkDestroy(_: *gtk.Box, self: *Tab) callconv(.C) void {
fn gtkDestroy(_: *gtk.Box, self: *Tab) callconv(.c) void {
log.debug("tab box destroy", .{});
const alloc = self.window.app.core_app.alloc;

View File

@ -227,7 +227,7 @@ pub fn createWindow(window: *Window) !*Window {
return new_window;
}
fn adwPageAttached(_: *adw.TabView, page: *adw.TabPage, _: c_int, self: *TabView) callconv(.C) void {
fn adwPageAttached(_: *adw.TabView, page: *adw.TabPage, _: c_int, self: *TabView) callconv(.c) void {
const child = page.getChild().as(gobject.Object);
const tab: *Tab = @ptrCast(@alignCast(child.getData(Tab.GHOSTTY_TAB) orelse return));
tab.window = self.window;
@ -239,7 +239,7 @@ fn adwClosePage(
_: *adw.TabView,
page: *adw.TabPage,
self: *TabView,
) callconv(.C) c_int {
) callconv(.c) c_int {
const child = page.getChild().as(gobject.Object);
const tab: *Tab = @ptrCast(@alignCast(child.getData(Tab.GHOSTTY_TAB) orelse return 0));
self.tab_view.closePageFinish(page, @intFromBool(self.forcing_close));
@ -251,7 +251,7 @@ fn adwClosePage(
fn adwTabViewCreateWindow(
_: *adw.TabView,
self: *TabView,
) callconv(.C) ?*adw.TabView {
) callconv(.c) ?*adw.TabView {
const window = createWindow(self.window) catch |err| {
log.warn("error creating new window error={}", .{err});
return null;
@ -259,7 +259,7 @@ fn adwTabViewCreateWindow(
return window.notebook.tab_view;
}
fn adwSelectPage(_: *adw.TabView, _: *gobject.ParamSpec, self: *TabView) callconv(.C) void {
fn adwSelectPage(_: *adw.TabView, _: *gobject.ParamSpec, self: *TabView) callconv(.c) void {
const page = self.tab_view.getSelectedPage() orelse return;
// If the tab was previously marked as needing attention

View File

@ -101,7 +101,7 @@ fn gtkLeftEnter(
_: f64,
_: f64,
right: *gtk.Label,
) callconv(.C) void {
) callconv(.c) void {
right.as(gtk.Widget).removeCssClass("hidden");
}
@ -110,6 +110,6 @@ fn gtkLeftEnter(
fn gtkLeftLeave(
_: *gtk.EventControllerMotion,
right: *gtk.Label,
) callconv(.C) void {
) callconv(.c) void {
right.as(gtk.Widget).addCssClass("hidden");
}

View File

@ -248,7 +248,7 @@ pub fn init(self: *Window, app: *App) !void {
btn.as(gtk.Widget).setTooltipText(i18n._("New Tab"));
btn.setDropdownTooltip(i18n._("New Split"));
var builder = Builder.init("menu-headerbar-split_menu", 1, 0, .blp);
var builder = Builder.init("menu-headerbar-split_menu", 1, 0);
defer builder.deinit();
btn.setMenuModel(builder.getObject(gio.MenuModel, "menu"));
@ -792,7 +792,7 @@ fn gtkWindowNotifyIsActive(
_: *adw.ApplicationWindow,
_: *gobject.ParamSpec,
self: *Window,
) callconv(.C) void {
) callconv(.c) void {
if (!self.isQuickTerminal()) return;
// Hide when we're unfocused
@ -883,7 +883,7 @@ fn adwTabOverviewOpen(
fn adwTabOverviewFocusTimer(
ud: ?*anyopaque,
) callconv(.C) c_int {
) callconv(.c) c_int {
if (!adw_version.supportsTabOverview()) unreachable;
const self: *Window = @ptrCast(@alignCast(ud orelse return 0));
self.adw_tab_overview_focus_timer = null;
@ -970,7 +970,7 @@ fn gtkActionAbout(
_: *gio.SimpleAction,
_: ?*glib.Variant,
self: *Window,
) callconv(.C) void {
) callconv(.c) void {
const name = "Ghostty";
const icon = "com.mitchellh.ghostty";
const website = "https://ghostty.org";
@ -1014,7 +1014,7 @@ fn gtkActionClose(
_: *gio.SimpleAction,
_: ?*glib.Variant,
self: *Window,
) callconv(.C) void {
) callconv(.c) void {
self.closeWithConfirmation();
}
@ -1022,7 +1022,7 @@ fn gtkActionNewWindow(
_: *gio.SimpleAction,
_: ?*glib.Variant,
self: *Window,
) callconv(.C) void {
) callconv(.c) void {
self.performBindingAction(.{ .new_window = {} });
}
@ -1030,7 +1030,7 @@ fn gtkActionNewTab(
_: *gio.SimpleAction,
_: ?*glib.Variant,
self: *Window,
) callconv(.C) void {
) callconv(.c) void {
self.performBindingAction(.{ .new_tab = {} });
}
@ -1038,7 +1038,7 @@ fn gtkActionCloseTab(
_: *gio.SimpleAction,
_: ?*glib.Variant,
self: *Window,
) callconv(.C) void {
) callconv(.c) void {
self.performBindingAction(.{ .close_tab = {} });
}
@ -1046,7 +1046,7 @@ fn gtkActionSplitRight(
_: *gio.SimpleAction,
_: ?*glib.Variant,
self: *Window,
) callconv(.C) void {
) callconv(.c) void {
self.performBindingAction(.{ .new_split = .right });
}
@ -1054,7 +1054,7 @@ fn gtkActionSplitDown(
_: *gio.SimpleAction,
_: ?*glib.Variant,
self: *Window,
) callconv(.C) void {
) callconv(.c) void {
self.performBindingAction(.{ .new_split = .down });
}
@ -1062,7 +1062,7 @@ fn gtkActionSplitLeft(
_: *gio.SimpleAction,
_: ?*glib.Variant,
self: *Window,
) callconv(.C) void {
) callconv(.c) void {
self.performBindingAction(.{ .new_split = .left });
}
@ -1070,7 +1070,7 @@ fn gtkActionSplitUp(
_: *gio.SimpleAction,
_: ?*glib.Variant,
self: *Window,
) callconv(.C) void {
) callconv(.c) void {
self.performBindingAction(.{ .new_split = .up });
}
@ -1078,7 +1078,7 @@ fn gtkActionToggleInspector(
_: *gio.SimpleAction,
_: ?*glib.Variant,
self: *Window,
) callconv(.C) void {
) callconv(.c) void {
self.performBindingAction(.{ .inspector = .toggle });
}
@ -1086,7 +1086,7 @@ fn gtkActionCopy(
_: *gio.SimpleAction,
_: ?*glib.Variant,
self: *Window,
) callconv(.C) void {
) callconv(.c) void {
self.performBindingAction(.{ .copy_to_clipboard = {} });
}
@ -1094,7 +1094,7 @@ fn gtkActionPaste(
_: *gio.SimpleAction,
_: ?*glib.Variant,
self: *Window,
) callconv(.C) void {
) callconv(.c) void {
self.performBindingAction(.{ .paste_from_clipboard = {} });
}
@ -1102,7 +1102,7 @@ fn gtkActionReset(
_: *gio.SimpleAction,
_: ?*glib.Variant,
self: *Window,
) callconv(.C) void {
) callconv(.c) void {
self.performBindingAction(.{ .reset = {} });
}
@ -1110,7 +1110,7 @@ fn gtkActionClear(
_: *gio.SimpleAction,
_: ?*glib.Variant,
self: *Window,
) callconv(.C) void {
) callconv(.c) void {
self.performBindingAction(.{ .clear_screen = {} });
}
@ -1118,7 +1118,7 @@ fn gtkActionPromptTitle(
_: *gio.SimpleAction,
_: ?*glib.Variant,
self: *Window,
) callconv(.C) void {
) callconv(.c) void {
self.performBindingAction(.{ .prompt_surface_title = {} });
}
@ -1133,7 +1133,7 @@ fn gtkTitlebarMenuActivate(
btn: *gtk.MenuButton,
_: *gobject.ParamSpec,
self: *Window,
) callconv(.C) void {
) callconv(.c) void {
// debian 12 is stuck on GTK 4.8
if (!gtk_version.atLeast(4, 10, 0)) return;
const active = btn.getActive() != 0;

View File

@ -4,62 +4,157 @@ pub const c = @cImport({
@cInclude("adwaita.h");
});
const adwaita_version = std.SemanticVersion{
.major = c.ADW_MAJOR_VERSION,
.minor = c.ADW_MINOR_VERSION,
.patch = c.ADW_MICRO_VERSION,
};
const required_blueprint_version = std.SemanticVersion{
.major = 0,
.minor = 16,
.patch = 0,
};
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const alloc = gpa.allocator();
var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
defer _ = debug_allocator.deinit();
const alloc = debug_allocator.allocator();
var it = try std.process.argsWithAllocator(alloc);
defer it.deinit();
_ = it.next();
const major = try std.fmt.parseUnsigned(u8, it.next() orelse return error.NoMajorVersion, 10);
const minor = try std.fmt.parseUnsigned(u8, it.next() orelse return error.NoMinorVersion, 10);
const required_adwaita_version = std.SemanticVersion{
.major = try std.fmt.parseUnsigned(u8, it.next() orelse return error.NoMajorVersion, 10),
.minor = try std.fmt.parseUnsigned(u8, it.next() orelse return error.NoMinorVersion, 10),
.patch = 0,
};
const output = it.next() orelse return error.NoOutput;
const input = it.next() orelse return error.NoInput;
if (c.ADW_MAJOR_VERSION < major or (c.ADW_MAJOR_VERSION == major and c.ADW_MINOR_VERSION < minor)) {
// If the Adwaita version is too old, generate an "empty" file.
const file = try std.fs.createFileAbsolute(output, .{
.truncate = true,
});
try file.writeAll(
\\<?xml version="1.0" encoding="UTF-8"?>
\\<interface domain="com.mitchellh.ghostty"/>
);
defer file.close();
return;
if (adwaita_version.order(required_adwaita_version) == .lt) {
std.debug.print(
\\`libadwaita` is too old.
\\
\\Ghostty requires a version {} or newer of `libadwaita` to
\\compile this blueprint. Please install it, ensure that it is
\\available on your PATH, and then retry building Ghostty.
, .{required_adwaita_version});
std.posix.exit(1);
}
var compiler = std.process.Child.init(
&.{
"blueprint-compiler",
"compile",
"--output",
output,
input,
},
alloc,
);
{
var stdout: std.ArrayListUnmanaged(u8) = .empty;
defer stdout.deinit(alloc);
var stderr: std.ArrayListUnmanaged(u8) = .empty;
defer stderr.deinit(alloc);
const term = compiler.spawnAndWait() catch |err| switch (err) {
error.FileNotFound => {
std.log.err(
\\`blueprint-compiler` not found.
var blueprint_compiler = std.process.Child.init(
&.{
"blueprint-compiler",
"--version",
},
alloc,
);
blueprint_compiler.stdout_behavior = .Pipe;
blueprint_compiler.stderr_behavior = .Pipe;
try blueprint_compiler.spawn();
try blueprint_compiler.collectOutput(
alloc,
&stdout,
&stderr,
std.math.maxInt(u16),
);
const term = blueprint_compiler.wait() catch |err| switch (err) {
error.FileNotFound => {
std.debug.print(
\\`blueprint-compiler` not found.
\\
\\Ghostty requires version {} or newer of
\\`blueprint-compiler` as a build-time dependency starting
\\from version 1.2. Please install it, ensure that it is
\\available on your PATH, and then retry building Ghostty.
\\
, .{required_blueprint_version});
std.posix.exit(1);
},
else => return err,
};
switch (term) {
.Exited => |rc| {
if (rc != 0) std.process.exit(1);
},
else => std.process.exit(1),
}
const version = try std.SemanticVersion.parse(std.mem.trim(u8, stdout.items, &std.ascii.whitespace));
if (version.order(required_blueprint_version) == .lt) {
std.debug.print(
\\`blueprint-compiler` is the wrong version.
\\
\\Ghostty requires `blueprint-compiler` as a build-time dependency starting from version 1.2.
\\Please install it, ensure that it is available on your PATH, and then retry building Ghostty.
, .{});
\\Ghostty requires version {} or newer of
\\`blueprint-compiler` as a build-time dependency starting
\\from version 1.2. Please install it, ensure that it is
\\available on your PATH, and then retry building Ghostty.
\\
, .{required_blueprint_version});
std.posix.exit(1);
},
else => return err,
};
}
}
switch (term) {
.Exited => |rc| {
if (rc != 0) std.process.exit(1);
},
else => std.process.exit(1),
{
var stdout: std.ArrayListUnmanaged(u8) = .empty;
defer stdout.deinit(alloc);
var stderr: std.ArrayListUnmanaged(u8) = .empty;
defer stderr.deinit(alloc);
var blueprint_compiler = std.process.Child.init(
&.{
"blueprint-compiler",
"compile",
"--output",
output,
input,
},
alloc,
);
blueprint_compiler.stdout_behavior = .Pipe;
blueprint_compiler.stderr_behavior = .Pipe;
try blueprint_compiler.spawn();
try blueprint_compiler.collectOutput(
alloc,
&stdout,
&stderr,
std.math.maxInt(u16),
);
const term = blueprint_compiler.wait() catch |err| switch (err) {
error.FileNotFound => {
std.debug.print(
\\`blueprint-compiler` not found.
\\
\\Ghostty requires version {} or newer of
\\`blueprint-compiler` as a build-time dependency starting
\\from version 1.2. Please install it, ensure that it is
\\available on your PATH, and then retry building Ghostty.
\\
, .{required_blueprint_version});
std.posix.exit(1);
},
else => return err,
};
switch (term) {
.Exited => |rc| {
if (rc != 0) {
std.debug.print("{s}", .{stderr.items});
std.process.exit(1);
}
},
else => {
std.debug.print("{s}", .{stderr.items});
std.process.exit(1);
},
}
}
}

View File

@ -1,32 +0,0 @@
const std = @import("std");
const build_options = @import("build_options");
const gtk = @import("gtk");
const adw = @import("adw");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const alloc = gpa.allocator();
const filename = filename: {
var it = try std.process.argsWithAllocator(alloc);
defer it.deinit();
_ = it.next() orelse return error.NoFilename;
break :filename try alloc.dupeZ(u8, it.next() orelse return error.NoFilename);
};
defer alloc.free(filename);
const data = try std.fs.cwd().readFileAllocOptions(alloc, filename, std.math.maxInt(u16), null, 1, 0);
defer alloc.free(data);
if (gtk.initCheck() == 0) {
std.debug.print("{s}: skipping builder check because we can't connect to display!\n", .{filename});
return;
}
adw.init();
const builder = gtk.Builder.newFromString(data.ptr, @intCast(data.len));
defer builder.unref();
}

View File

@ -53,19 +53,6 @@ const icons = [_]struct {
},
};
pub const VersionedBuilderXML = struct {
major: u16,
minor: u16,
name: []const u8,
};
pub const ui_files = [_]VersionedBuilderXML{
.{ .major = 1, .minor = 2, .name = "config-errors-dialog" },
.{ .major = 1, .minor = 2, .name = "ccw-osc-52-read" },
.{ .major = 1, .minor = 2, .name = "ccw-osc-52-write" },
.{ .major = 1, .minor = 2, .name = "ccw-paste" },
};
pub const VersionedBlueprint = struct {
major: u16,
minor: u16,
@ -81,16 +68,21 @@ pub const blueprint_files = [_]VersionedBlueprint{
.{ .major = 1, .minor = 5, .name = "ccw-osc-52-read" },
.{ .major = 1, .minor = 5, .name = "ccw-osc-52-write" },
.{ .major = 1, .minor = 5, .name = "ccw-paste" },
.{ .major = 1, .minor = 2, .name = "config-errors-dialog" },
.{ .major = 1, .minor = 2, .name = "ccw-osc-52-read" },
.{ .major = 1, .minor = 2, .name = "ccw-osc-52-write" },
.{ .major = 1, .minor = 2, .name = "ccw-paste" },
};
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const alloc = gpa.allocator();
var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
defer _ = debug_allocator.deinit();
const alloc = debug_allocator.allocator();
var extra_ui_files = std.ArrayList([]const u8).init(alloc);
var extra_ui_files: std.ArrayListUnmanaged([]const u8) = .empty;
defer {
for (extra_ui_files.items) |item| alloc.free(item);
extra_ui_files.deinit();
extra_ui_files.deinit(alloc);
}
var it = try std.process.argsWithAllocator(alloc);
@ -98,7 +90,7 @@ pub fn main() !void {
while (it.next()) |argument| {
if (std.mem.eql(u8, std.fs.path.extension(argument), ".ui")) {
try extra_ui_files.append(try alloc.dupe(u8, argument));
try extra_ui_files.append(alloc, try alloc.dupe(u8, argument));
}
}
@ -132,16 +124,11 @@ pub fn main() !void {
\\ <gresource prefix="/com/mitchellh/ghostty/ui">
\\
);
for (ui_files) |ui_file| {
try writer.print(
" <file compressed=\"true\" preprocess=\"xml-stripblanks\" alias=\"{0d}.{1d}/{2s}.ui\">src/apprt/gtk/ui/{0d}.{1d}/{2s}.ui</file>\n",
.{ ui_file.major, ui_file.minor, ui_file.name },
);
}
for (extra_ui_files.items) |ui_file| {
const stem = std.fs.path.stem(ui_file);
for (blueprint_files) |file| {
if (!std.mem.eql(u8, file.name, stem)) continue;
const expected = try std.fmt.allocPrint(alloc, "/{d}.{d}/{s}.ui", .{ file.major, file.minor, file.name });
defer alloc.free(expected);
if (!std.mem.endsWith(u8, ui_file, expected)) continue;
try writer.print(
" <file compressed=\"true\" preprocess=\"xml-stripblanks\" alias=\"{d}.{d}/{s}.ui\">{s}</file>\n",
.{ file.major, file.minor, file.name, ui_file },
@ -157,7 +144,7 @@ pub fn main() !void {
}
pub const dependencies = deps: {
const total = css_files.len + icons.len + ui_files.len + blueprint_files.len;
const total = css_files.len + icons.len + blueprint_files.len;
var deps: [total][]const u8 = undefined;
var index: usize = 0;
for (css_files) |css_file| {
@ -168,14 +155,6 @@ pub const dependencies = deps: {
deps[index] = std.fmt.comptimePrint("images/icons/icon_{s}.png", .{icon.source});
index += 1;
}
for (ui_files) |ui_file| {
deps[index] = std.fmt.comptimePrint("src/apprt/gtk/ui/{d}.{d}/{s}.ui", .{
ui_file.major,
ui_file.minor,
ui_file.name,
});
index += 1;
}
for (blueprint_files) |blueprint_file| {
deps[index] = std.fmt.comptimePrint("src/apprt/gtk/ui/{d}.{d}/{s}.blp", .{
blueprint_file.major,

View File

@ -87,10 +87,23 @@ pub inline fn runtimeAtLeast(
}) != .lt;
}
pub inline fn runtimeUntil(
comptime major: u16,
comptime minor: u16,
comptime micro: u16,
) bool {
const runtime_version = getRuntimeVersion();
return runtime_version.order(.{
.major = major,
.minor = minor,
.patch = micro,
}) == .lt;
}
test "atLeast" {
const testing = std.testing;
const funs = &.{ atLeast, runtimeAtLeast };
const funs = &.{ atLeast, runtimeAtLeast, runtimeUntil };
inline for (funs) |fun| {
try testing.expect(fun(c.GTK_MAJOR_VERSION, c.GTK_MINOR_VERSION, c.GTK_MICRO_VERSION));

View File

@ -177,7 +177,7 @@ const Window = struct {
}
/// "destroy" signal for the window
fn gtkDestroy(_: *gtk.ApplicationWindow, self: *Window) callconv(.C) void {
fn gtkDestroy(_: *gtk.ApplicationWindow, self: *Window) callconv(.c) void {
log.debug("window destroy", .{});
self.deinit();
}

View File

@ -41,7 +41,7 @@ pub fn Menu(
else => unreachable,
};
var builder = Builder.init("menu-" ++ object_type ++ "-" ++ menu_name, 1, 0, .blp);
var builder = Builder.init("menu-" ++ object_type ++ "-" ++ menu_name, 1, 0);
defer builder.deinit();
const menu_model = builder.getObject(gio.MenuModel, "menu").?;
@ -130,7 +130,7 @@ pub fn Menu(
}
/// Refocus tab that lost focus because of the popover menu
fn gtkRefocusTerm(_: *gtk.PopoverMenu, self: *Self) callconv(.C) void {
fn gtkRefocusTerm(_: *gtk.PopoverMenu, self: *Self) callconv(.c) void {
const window: *Window = switch (T) {
Window => self.parent,
Surface => self.parent.container.window() orelse return,

View File

@ -1,77 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
DO NOT EDIT!
This file was @generated by blueprint-compiler. Instead, edit the
corresponding .blp file and regenerate this file with blueprint-compiler.
-->
<interface domain="com.mitchellh.ghostty">
<requires lib="gtk" version="4.0"/>
<object class="AdwMessageDialog" id="clipboard_confirmation_window">
<property name="heading" translatable="true">Authorize Clipboard Access</property>
<property name="body" translatable="true">An application is attempting to read from the clipboard. The current clipboard contents are shown below.</property>
<responses>
<response id="cancel" translatable="true" appearance="suggested">Deny</response>
<response id="ok" translatable="true" appearance="destructive">Allow</response>
</responses>
<property name="default-response">cancel</property>
<property name="close-response">cancel</property>
<property name="extra-child">
<object class="GtkOverlay">
<style>
<class name="osd"/>
</style>
<child>
<object class="GtkScrolledWindow" id="text_view_scroll">
<property name="width-request">500</property>
<property name="height-request">250</property>
<child>
<object class="GtkTextView" id="text_view">
<property name="cursor-visible">false</property>
<property name="editable">false</property>
<property name="monospace">true</property>
<property name="top-margin">8</property>
<property name="left-margin">8</property>
<property name="bottom-margin">8</property>
<property name="right-margin">8</property>
<style>
<class name="clipboard-content-view"/>
</style>
</object>
</child>
</object>
</child>
<child type="overlay">
<object class="GtkButton" id="reveal_button">
<property name="visible">false</property>
<property name="halign">2</property>
<property name="valign">1</property>
<property name="margin-end">12</property>
<property name="margin-top">12</property>
<child>
<object class="GtkImage">
<property name="icon-name">view-reveal-symbolic</property>
</object>
</child>
</object>
</child>
<child type="overlay">
<object class="GtkButton" id="hide_button">
<property name="visible">false</property>
<property name="halign">2</property>
<property name="valign">1</property>
<property name="margin-end">12</property>
<property name="margin-top">12</property>
<style>
<class name="opaque"/>
</style>
<child>
<object class="GtkImage">
<property name="icon-name">view-conceal-symbolic</property>
</object>
</child>
</object>
</child>
</object>
</property>
</object>
</interface>

View File

@ -1,77 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
DO NOT EDIT!
This file was @generated by blueprint-compiler. Instead, edit the
corresponding .blp file and regenerate this file with blueprint-compiler.
-->
<interface domain="com.mitchellh.ghostty">
<requires lib="gtk" version="4.0"/>
<object class="AdwMessageDialog" id="clipboard_confirmation_window">
<property name="heading" translatable="true">Authorize Clipboard Access</property>
<property name="body" translatable="true">An application is attempting to write to the clipboard. The current clipboard contents are shown below.</property>
<responses>
<response id="cancel" translatable="true" appearance="suggested">Deny</response>
<response id="ok" translatable="true" appearance="destructive">Allow</response>
</responses>
<property name="default-response">cancel</property>
<property name="close-response">cancel</property>
<property name="extra-child">
<object class="GtkOverlay">
<style>
<class name="osd"/>
</style>
<child>
<object class="GtkScrolledWindow" id="text_view_scroll">
<property name="width-request">500</property>
<property name="height-request">250</property>
<child>
<object class="GtkTextView" id="text_view">
<property name="cursor-visible">false</property>
<property name="editable">false</property>
<property name="monospace">true</property>
<property name="top-margin">8</property>
<property name="left-margin">8</property>
<property name="bottom-margin">8</property>
<property name="right-margin">8</property>
<style>
<class name="clipboard-content-view"/>
</style>
</object>
</child>
</object>
</child>
<child type="overlay">
<object class="GtkButton" id="reveal_button">
<property name="visible">false</property>
<property name="halign">2</property>
<property name="valign">1</property>
<property name="margin-end">12</property>
<property name="margin-top">12</property>
<child>
<object class="GtkImage">
<property name="icon-name">view-reveal-symbolic</property>
</object>
</child>
</object>
</child>
<child type="overlay">
<object class="GtkButton" id="hide_button">
<property name="visible">false</property>
<property name="halign">2</property>
<property name="valign">1</property>
<property name="margin-end">12</property>
<property name="margin-top">12</property>
<style>
<class name="opaque"/>
</style>
<child>
<object class="GtkImage">
<property name="icon-name">view-conceal-symbolic</property>
</object>
</child>
</object>
</child>
</object>
</property>
</object>
</interface>

View File

@ -1,77 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
DO NOT EDIT!
This file was @generated by blueprint-compiler. Instead, edit the
corresponding .blp file and regenerate this file with blueprint-compiler.
-->
<interface domain="com.mitchellh.ghostty">
<requires lib="gtk" version="4.0"/>
<object class="AdwMessageDialog" id="clipboard_confirmation_window">
<property name="heading" translatable="true">Warning: Potentially Unsafe Paste</property>
<property name="body" translatable="true">Pasting this text into the terminal may be dangerous as it looks like some commands may be executed.</property>
<responses>
<response id="cancel" translatable="true" appearance="suggested">Cancel</response>
<response id="ok" translatable="true" appearance="destructive">Paste</response>
</responses>
<property name="default-response">cancel</property>
<property name="close-response">cancel</property>
<property name="extra-child">
<object class="GtkOverlay">
<style>
<class name="osd"/>
</style>
<child>
<object class="GtkScrolledWindow" id="text_view_scroll">
<property name="width-request">500</property>
<property name="height-request">250</property>
<child>
<object class="GtkTextView" id="text_view">
<property name="cursor-visible">false</property>
<property name="editable">false</property>
<property name="monospace">true</property>
<property name="top-margin">8</property>
<property name="left-margin">8</property>
<property name="bottom-margin">8</property>
<property name="right-margin">8</property>
<style>
<class name="clipboard-content-view"/>
</style>
</object>
</child>
</object>
</child>
<child type="overlay">
<object class="GtkButton" id="reveal_button">
<property name="visible">false</property>
<property name="halign">2</property>
<property name="valign">1</property>
<property name="margin-end">12</property>
<property name="margin-top">12</property>
<child>
<object class="GtkImage">
<property name="icon-name">view-reveal-symbolic</property>
</object>
</child>
</object>
</child>
<child type="overlay">
<object class="GtkButton" id="hide_button">
<property name="visible">false</property>
<property name="halign">2</property>
<property name="valign">1</property>
<property name="margin-end">12</property>
<property name="margin-top">12</property>
<style>
<class name="opaque"/>
</style>
<child>
<object class="GtkImage">
<property name="icon-name">view-conceal-symbolic</property>
</object>
</child>
</object>
</child>
</object>
</property>
</object>
</interface>

View File

@ -1,36 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
DO NOT EDIT!
This file was @generated by blueprint-compiler. Instead, edit the
corresponding .blp file and regenerate this file with blueprint-compiler.
-->
<interface>
<requires lib="gtk" version="4.0"/>
<object class="AdwMessageDialog" id="config_errors_dialog">
<property name="heading" translatable="yes">Configuration Errors</property>
<property name="body" translatable="yes">One or more configuration errors were found. Please review the errors below, and either reload your configuration or ignore these errors.</property>
<responses>
<response id="ignore" translatable="yes">Ignore</response>
<response id="reload" translatable="yes" appearance="suggested">Reload Configuration</response>
</responses>
<property name="extra-child">
<object class="GtkScrolledWindow">
<property name="min-content-width">500</property>
<property name="min-content-height">100</property>
<child>
<object class="GtkTextView">
<property name="editable">false</property>
<property name="cursor-visible">false</property>
<property name="top-margin">8</property>
<property name="bottom-margin">8</property>
<property name="left-margin">8</property>
<property name="right-margin">8</property>
<property name="buffer">
<object class="GtkTextBuffer" id="error_message"></object>
</property>
</object>
</child>
</object>
</property>
</object>
</interface>

View File

@ -1,21 +1,15 @@
# GTK UI files
This directory is for storing GTK resource definitions. With one exception, the
files should be be in the Blueprint markup language.
This directory is for storing GTK blueprints. GTK blueprints are compiled into
GTK resource builder `.ui` files by `blueprint-compiler` at build time and then
converted into an embeddable resource by `glib-compile-resources`.
Resource files should be stored in directories that represent the minimum
Adwaita version needed to use that resource. Resource files should also be
formatted using `blueprint-compiler format` as well to ensure consistency.
Blueprint files should be stored in directories that represent the minimum
Adwaita version needed to use that resource. Blueprint files should also be
formatted using `blueprint-compiler format` as well to ensure consistency
(formatting will be checked in CI).
The one exception to files being in Blueprint markup language is when Adwaita
features are used that the `blueprint-compiler` on a supported platform does not
compile. For example, Debian 12 includes Adwaita 1.2 and `blueprint-compiler`
0.6.0. Adwaita 1.2 includes support for `MessageDialog` but `blueprint-compiler`
0.6.0 does not. In cases like that the Blueprint markup should be compiled on a
platform that provides a new enough `blueprint-compiler` and the resulting `.ui`
file should be committed to the Ghostty source code. Care should be taken that
the `.blp` file and the `.ui` file remain in sync.
In all other cases only the `.blp` should be committed to the Ghostty source
code. The build process will use `blueprint-compiler` to generate the `.ui`
files necessary at runtime.
`blueprint-compiler` version 0.16.0 or newer is required to compile Blueprint
files. If your system does not have `blueprint-compiler` or does not have a
new enough version you can use the generated source tarballs, which contain
precompiled versions of the blueprints.

View File

@ -410,7 +410,7 @@ pub const Window = struct {
_: *gdk.Surface,
monitor: *gdk.Monitor,
apprt_window: *ApprtWindow,
) callconv(.C) void {
) callconv(.c) void {
const window = apprt_window.window.as(gtk.Window);
const size = apprt_window.config.quick_terminal_size;
const position = apprt_window.config.quick_terminal_position;

View File

@ -662,34 +662,6 @@ fn addGTK(
}
{
// For our actual build, we validate our GTK builder files if we can.
{
const gtk_builder_check = b.addExecutable(.{
.name = "gtk_builder_check",
.root_source_file = b.path("src/apprt/gtk/builder_check.zig"),
.target = b.graph.host,
});
gtk_builder_check.root_module.addOptions("build_options", self.options);
if (gobject_) |gobject| {
gtk_builder_check.root_module.addImport(
"gtk",
gobject.module("gtk4"),
);
gtk_builder_check.root_module.addImport(
"adw",
gobject.module("adw1"),
);
}
for (gresource.dependencies) |pathname| {
const extension = std.fs.path.extension(pathname);
if (!std.mem.eql(u8, extension, ".ui")) continue;
const check = b.addRunArtifact(gtk_builder_check);
check.addFileArg(b.path(pathname));
step.step.dependOn(&check.step);
}
}
// Get our gresource c/h files and add them to our build.
const dist = gtkDistResources(b);
step.addCSourceFile(.{ .file = dist.resources_c.path(b), .flags = &.{} });

View File

@ -24,6 +24,9 @@ pub const Options = struct {
/// If true, force a plain list of themes.
plain: bool = false,
/// Specifies the color scheme of the themes to include in the list.
color: enum { all, dark, light } = .all,
pub fn deinit(self: Options) void {
_ = self;
}
@ -93,6 +96,9 @@ const ThemeListElement = struct {
/// * `--path`: Show the full path to the theme.
///
/// * `--plain`: Force a plain listing of themes.
///
/// * `--color`: Specify the color scheme of the themes included in the list.
/// This can be `dark`, `light`, or `all`. The default is `all`.
pub fn run(gpa_alloc: std.mem.Allocator) !u8 {
var opts: Options = .{};
defer opts.deinit();
@ -137,11 +143,30 @@ pub fn run(gpa_alloc: std.mem.Allocator) !u8 {
if (std.mem.eql(u8, entry.name, ".DS_Store"))
continue;
count += 1;
try themes.append(.{
.location = loc.location,
.path = try std.fs.path.join(alloc, &.{ loc.dir, entry.name }),
.theme = try alloc.dupe(u8, entry.name),
});
const path = try std.fs.path.join(alloc, &.{ loc.dir, entry.name });
// if there is no need to filter just append the theme to the list
if (opts.color == .all) {
try themes.append(.{
.path = path,
.location = loc.location,
.theme = try alloc.dupe(u8, entry.name),
});
continue;
}
// otherwise check if the theme should be included based on the provided options
var config = try Config.default(alloc);
defer config.deinit();
try config.loadFile(config._arena.?.allocator(), path);
if (shouldIncludeTheme(opts, config)) {
try themes.append(.{
.path = path,
.location = loc.location,
.theme = try alloc.dupe(u8, entry.name),
});
}
},
else => {},
}
@ -1594,3 +1619,13 @@ fn preview(allocator: std.mem.Allocator, themes: []ThemeListElement) !void {
defer app.deinit();
try app.run();
}
fn shouldIncludeTheme(opts: Options, theme_config: Config) bool {
const rf = @as(f32, @floatFromInt(theme_config.background.r)) / 255.0;
const gf = @as(f32, @floatFromInt(theme_config.background.g)) / 255.0;
const bf = @as(f32, @floatFromInt(theme_config.background.b)) / 255.0;
const luminance = 0.2126 * rf + 0.7152 * gf + 0.0722 * bf;
const is_dark = luminance < 0.5;
return (opts.color == .dark and is_dark) or (opts.color == .light and !is_dark);
}

View File

@ -2008,7 +2008,7 @@ keybind: Keybinds = .{},
/// macOS doesn't have a distinct "alt" key and instead has the "option"
/// key which behaves slightly differently. On macOS by default, the
/// option key plus a character will sometimes produces a Unicode character.
/// option key plus a character will sometimes produce a Unicode character.
/// For example, on US standard layouts option-b produces "". This may be
/// undesirable if you want to use "option" as an "alt" key for keybindings
/// in terminal programs or shells.

View File

@ -166,7 +166,7 @@ fn beforeSend(
event_val: sentry.c.sentry_value_t,
_: ?*anyopaque,
_: ?*anyopaque,
) callconv(.C) sentry.c.sentry_value_t {
) callconv(.c) sentry.c.sentry_value_t {
// The native SDK at the time of writing doesn't support thread-local
// scopes. The full SDK has one global scope. So we use the beforeSend
// handler to set thread-specific data such as window size, grid size,
@ -237,7 +237,7 @@ fn beforeSend(
}
pub const Transport = struct {
pub fn send(envelope: *sentry.Envelope, ud: ?*anyopaque) callconv(.C) void {
pub fn send(envelope: *sentry.Envelope, ud: ?*anyopaque) callconv(.c) void {
_ = ud;
defer envelope.deinit();

View File

@ -380,7 +380,7 @@ test getIndex {
const testEmoji = font.embedded.emoji;
const testEmojiText = font.embedded.emoji_text;
var lib = try Library.init();
var lib = try Library.init(alloc);
defer lib.deinit();
var c = Collection.init();
@ -461,7 +461,7 @@ test "getIndex disabled font style" {
var atlas_grayscale = try font.Atlas.init(alloc, 512, .grayscale);
defer atlas_grayscale.deinit(alloc);
var lib = try Library.init();
var lib = try Library.init(alloc);
defer lib.deinit();
var c = Collection.init();
@ -513,7 +513,7 @@ test "getIndex box glyph" {
const testing = std.testing;
const alloc = testing.allocator;
var lib = try Library.init();
var lib = try Library.init(alloc);
defer lib.deinit();
const c = Collection.init();

View File

@ -78,8 +78,8 @@ pub const AddError = Allocator.Error || error{
/// next in priority if others exist already, i.e. it'll be the _last_ to be
/// searched for a glyph in that list.
///
/// The collection takes ownership of the face. The face will be deallocated
/// when the collection is deallocated.
/// If no error is encountered then the collection takes ownership of the face,
/// in which case face will be deallocated when the collection is deallocated.
///
/// If a loaded face is added to the collection, it should be the same
/// size as all the other faces in the collection. This function will not
@ -700,7 +700,7 @@ test "add full" {
const alloc = testing.allocator;
const testFont = font.embedded.regular;
var lib = try Library.init();
var lib = try Library.init(alloc);
defer lib.deinit();
var c = init();
@ -714,15 +714,18 @@ test "add full" {
) });
}
try testing.expectError(error.CollectionFull, c.add(
alloc,
.regular,
.{ .loaded = try Face.init(
lib,
testFont,
.{ .size = .{ .points = 12 } },
) },
));
var face = try Face.init(
lib,
testFont,
.{ .size = .{ .points = 12 } },
);
// We have to deinit it manually since the
// collection doesn't do it if adding fails.
defer face.deinit();
try testing.expectError(
error.CollectionFull,
c.add(alloc, .regular, .{ .loaded = face }),
);
}
test "add deferred without loading options" {
@ -746,7 +749,7 @@ test getFace {
const alloc = testing.allocator;
const testFont = font.embedded.regular;
var lib = try Library.init();
var lib = try Library.init(alloc);
defer lib.deinit();
var c = init();
@ -770,7 +773,7 @@ test getIndex {
const alloc = testing.allocator;
const testFont = font.embedded.regular;
var lib = try Library.init();
var lib = try Library.init(alloc);
defer lib.deinit();
var c = init();
@ -801,7 +804,7 @@ test completeStyles {
const alloc = testing.allocator;
const testFont = font.embedded.regular;
var lib = try Library.init();
var lib = try Library.init(alloc);
defer lib.deinit();
var c = init();
@ -828,7 +831,7 @@ test setSize {
const alloc = testing.allocator;
const testFont = font.embedded.regular;
var lib = try Library.init();
var lib = try Library.init(alloc);
defer lib.deinit();
var c = init();
@ -851,7 +854,7 @@ test hasCodepoint {
const alloc = testing.allocator;
const testFont = font.embedded.regular;
var lib = try Library.init();
var lib = try Library.init(alloc);
defer lib.deinit();
var c = init();
@ -875,7 +878,7 @@ test "hasCodepoint emoji default graphical" {
const alloc = testing.allocator;
const testEmoji = font.embedded.emoji;
var lib = try Library.init();
var lib = try Library.init(alloc);
defer lib.deinit();
var c = init();
@ -898,7 +901,7 @@ test "metrics" {
const alloc = testing.allocator;
const testFont = font.embedded.inconsolata;
var lib = try Library.init();
var lib = try Library.init(alloc);
defer lib.deinit();
var c = init();

View File

@ -407,7 +407,7 @@ test "fontconfig" {
const alloc = testing.allocator;
// Load freetype
var lib = try Library.init();
var lib = try Library.init(alloc);
defer lib.deinit();
// Get a deferred face from fontconfig
@ -425,7 +425,8 @@ test "fontconfig" {
try testing.expect(n.len > 0);
// Load it and verify it works
const face = try def.load(lib, .{ .size = .{ .points = 12 } });
var face = try def.load(lib, .{ .size = .{ .points = 12 } });
defer face.deinit();
try testing.expect(face.glyphIndex(' ') != null);
}
@ -437,7 +438,7 @@ test "coretext" {
const alloc = testing.allocator;
// Load freetype
var lib = try Library.init();
var lib = try Library.init(alloc);
defer lib.deinit();
// Get a deferred face from fontconfig
@ -456,6 +457,7 @@ test "coretext" {
try testing.expect(n.len > 0);
// Load it and verify it works
const face = try def.load(lib, .{ .size = .{ .points = 12 } });
var face = try def.load(lib, .{ .size = .{ .points = 12 } });
defer face.deinit();
try testing.expect(face.glyphIndex(' ') != null);
}

View File

@ -338,7 +338,7 @@ test getIndex {
const alloc = testing.allocator;
// const testEmoji = @import("test.zig").fontEmoji;
var lib = try Library.init();
var lib = try Library.init(alloc);
defer lib.deinit();
var grid = try testGrid(.normal, alloc, lib);

View File

@ -50,7 +50,7 @@ pub const InitError = Library.InitError;
/// Initialize a new SharedGridSet.
pub fn init(alloc: Allocator) InitError!SharedGridSet {
var font_lib = try Library.init();
var font_lib = try Library.init(alloc);
errdefer font_lib.deinit();
return .{

View File

@ -46,7 +46,11 @@ pub const Face = struct {
};
/// Initialize a CoreText-based font from a TTF/TTC in memory.
pub fn init(lib: font.Library, source: [:0]const u8, opts: font.face.Options) !Face {
pub fn init(
lib: font.Library,
source: [:0]const u8,
opts: font.face.Options,
) !Face {
_ = lib;
const data = try macos.foundation.Data.createWithBytesNoCopy(source);
@ -914,7 +918,7 @@ test "in-memory" {
var atlas = try font.Atlas.init(alloc, 512, .grayscale);
defer atlas.deinit(alloc);
var lib = try font.Library.init();
var lib = try font.Library.init(alloc);
defer lib.deinit();
var face = try Face.init(lib, testFont, .{ .size = .{ .points = 12 } });
@ -941,7 +945,7 @@ test "variable" {
var atlas = try font.Atlas.init(alloc, 512, .grayscale);
defer atlas.deinit(alloc);
var lib = try font.Library.init();
var lib = try font.Library.init(alloc);
defer lib.deinit();
var face = try Face.init(lib, testFont, .{ .size = .{ .points = 12 } });
@ -968,7 +972,7 @@ test "variable set variation" {
var atlas = try font.Atlas.init(alloc, 512, .grayscale);
defer atlas.deinit(alloc);
var lib = try font.Library.init();
var lib = try font.Library.init(alloc);
defer lib.deinit();
var face = try Face.init(lib, testFont, .{ .size = .{ .points = 12 } });
@ -996,7 +1000,7 @@ test "svg font table" {
const alloc = testing.allocator;
const testFont = font.embedded.julia_mono;
var lib = try font.Library.init();
var lib = try font.Library.init(alloc);
defer lib.deinit();
var face = try Face.init(lib, testFont, .{ .size = .{ .points = 12 } });
@ -1010,9 +1014,10 @@ test "svg font table" {
test "glyphIndex colored vs text" {
const testing = std.testing;
const alloc = testing.allocator;
const testFont = font.embedded.julia_mono;
var lib = try font.Library.init();
var lib = try font.Library.init(alloc);
defer lib.deinit();
var face = try Face.init(lib, testFont, .{ .size = .{ .points = 12 } });

View File

@ -29,12 +29,20 @@ pub const Face = struct {
assert(font.face.FreetypeLoadFlags != void);
}
/// Our freetype library
lib: freetype.Library,
/// Our Library
lib: Library,
/// Our font face.
face: freetype.Face,
/// This mutex MUST be held while doing anything with the
/// glyph slot on the freetype face, because this struct
/// may be shared across multiple surfaces.
///
/// This means that anywhere where `self.face.loadGlyph`
/// is called, this mutex must be held.
ft_mutex: *std.Thread.Mutex,
/// Harfbuzz font corresponding to this face.
hb_font: harfbuzz.Font,
@ -59,30 +67,52 @@ pub const Face = struct {
};
/// Initialize a new font face with the given source in-memory.
pub fn initFile(lib: Library, path: [:0]const u8, index: i32, opts: font.face.Options) !Face {
pub fn initFile(
lib: Library,
path: [:0]const u8,
index: i32,
opts: font.face.Options,
) !Face {
lib.mutex.lock();
defer lib.mutex.unlock();
const face = try lib.lib.initFace(path, index);
errdefer face.deinit();
return try initFace(lib, face, opts);
}
/// Initialize a new font face with the given source in-memory.
pub fn init(lib: Library, source: [:0]const u8, opts: font.face.Options) !Face {
pub fn init(
lib: Library,
source: [:0]const u8,
opts: font.face.Options,
) !Face {
lib.mutex.lock();
defer lib.mutex.unlock();
const face = try lib.lib.initMemoryFace(source, 0);
errdefer face.deinit();
return try initFace(lib, face, opts);
}
fn initFace(lib: Library, face: freetype.Face, opts: font.face.Options) !Face {
fn initFace(
lib: Library,
face: freetype.Face,
opts: font.face.Options,
) !Face {
try face.selectCharmap(.unicode);
try setSize_(face, opts.size);
var hb_font = try harfbuzz.freetype.createFont(face.handle);
errdefer hb_font.destroy();
const ft_mutex = try lib.alloc.create(std.Thread.Mutex);
errdefer lib.alloc.destroy(ft_mutex);
ft_mutex.* = .{};
var result: Face = .{
.lib = lib.lib,
.lib = lib,
.face = face,
.hb_font = hb_font,
.ft_mutex = ft_mutex,
.load_flags = opts.freetype_load_flags,
};
result.quirks_disable_default_font_features = quirks.disableDefaultFontFeatures(&result);
@ -114,7 +144,13 @@ pub const Face = struct {
}
pub fn deinit(self: *Face) void {
self.face.deinit();
self.lib.alloc.destroy(self.ft_mutex);
{
self.lib.mutex.lock();
defer self.lib.mutex.unlock();
self.face.deinit();
}
self.hb_font.destroy();
self.* = undefined;
}
@ -147,11 +183,7 @@ pub const Face = struct {
self.face.ref();
errdefer self.face.deinit();
var f = try initFace(
.{ .lib = self.lib },
self.face,
opts,
);
var f = try initFace(self.lib, self.face, opts);
errdefer f.deinit();
f.synthetic = self.synthetic;
f.synthetic.bold = true;
@ -166,11 +198,7 @@ pub const Face = struct {
self.face.ref();
errdefer self.face.deinit();
var f = try initFace(
.{ .lib = self.lib },
self.face,
opts,
);
var f = try initFace(self.lib, self.face, opts);
errdefer f.deinit();
f.synthetic = self.synthetic;
f.synthetic.italic = true;
@ -228,7 +256,7 @@ pub const Face = struct {
// first thing we have to do is get all the vars and put them into
// an array.
const mm = try self.face.getMMVar();
defer self.lib.doneMMVar(mm);
defer self.lib.lib.doneMMVar(mm);
// To avoid allocations, we cap the number of variation axes we can
// support. This is arbitrary but Firefox caps this at 16 so I
@ -270,6 +298,9 @@ pub const Face = struct {
/// Returns true if the given glyph ID is colorized.
pub fn isColorGlyph(self: *const Face, glyph_id: u32) bool {
self.ft_mutex.lock();
defer self.ft_mutex.unlock();
// Load the glyph and see what pixel mode it renders with.
// All modes other than BGRA are non-color.
// If the glyph fails to load, just return false.
@ -296,6 +327,9 @@ pub const Face = struct {
glyph_index: u32,
opts: font.face.RenderOptions,
) !Glyph {
self.ft_mutex.lock();
defer self.ft_mutex.unlock();
const metrics = opts.grid_metrics;
// If we have synthetic italic, then we apply a transformation matrix.
@ -741,6 +775,9 @@ pub const Face = struct {
// If we fail to load any visible ASCII we just use max_advance from
// the metrics provided by FreeType.
const cell_width: f64 = cell_width: {
self.ft_mutex.lock();
defer self.ft_mutex.unlock();
var max: f64 = 0.0;
var c: u8 = ' ';
while (c < 127) : (c += 1) {
@ -780,6 +817,8 @@ pub const Face = struct {
break :heights .{
cap: {
self.ft_mutex.lock();
defer self.ft_mutex.unlock();
if (face.getCharIndex('H')) |glyph_index| {
if (face.loadGlyph(glyph_index, .{
.render = true,
@ -791,6 +830,8 @@ pub const Face = struct {
break :cap null;
},
ex: {
self.ft_mutex.lock();
defer self.ft_mutex.unlock();
if (face.getCharIndex('x')) |glyph_index| {
if (face.loadGlyph(glyph_index, .{
.render = true,
@ -832,7 +873,7 @@ test {
const testFont = font.embedded.inconsolata;
const alloc = testing.allocator;
var lib = try Library.init();
var lib = try Library.init(alloc);
defer lib.deinit();
var atlas = try font.Atlas.init(alloc, 512, .grayscale);
@ -881,7 +922,7 @@ test "color emoji" {
const alloc = testing.allocator;
const testFont = font.embedded.emoji;
var lib = try Library.init();
var lib = try Library.init(alloc);
defer lib.deinit();
var atlas = try font.Atlas.init(alloc, 512, .rgba);
@ -936,7 +977,7 @@ test "mono to rgba" {
const alloc = testing.allocator;
const testFont = font.embedded.emoji;
var lib = try Library.init();
var lib = try Library.init(alloc);
defer lib.deinit();
var atlas = try font.Atlas.init(alloc, 512, .rgba);
@ -958,7 +999,7 @@ test "svg font table" {
const alloc = testing.allocator;
const testFont = font.embedded.julia_mono;
var lib = try font.Library.init();
var lib = try font.Library.init(alloc);
defer lib.deinit();
var face = try Face.init(lib, testFont, .{ .size = .{ .points = 12, .xdpi = 72, .ydpi = 72 } });
@ -995,7 +1036,7 @@ test "bitmap glyph" {
const alloc = testing.allocator;
const testFont = font.embedded.terminus_ttf;
var lib = try Library.init();
var lib = try Library.init(alloc);
defer lib.deinit();
var atlas = try font.Atlas.init(alloc, 512, .grayscale);

View File

@ -1,5 +1,7 @@
//! A library represents the shared state that the underlying font
//! library implementation(s) require per-process.
const std = @import("std");
const Allocator = std.mem.Allocator;
const builtin = @import("builtin");
const options = @import("main.zig").options;
const freetype = @import("freetype");
@ -24,13 +26,26 @@ pub const Library = switch (options.backend) {
pub const FreetypeLibrary = struct {
lib: freetype.Library,
pub const InitError = freetype.Error;
alloc: Allocator,
pub fn init() InitError!Library {
return Library{ .lib = try freetype.Library.init() };
/// Mutex to be held any time the library is
/// being used to create or destroy a face.
mutex: *std.Thread.Mutex,
pub const InitError = freetype.Error || Allocator.Error;
pub fn init(alloc: Allocator) InitError!Library {
const lib = try freetype.Library.init();
errdefer lib.deinit();
const mutex = try alloc.create(std.Thread.Mutex);
mutex.* = .{};
return Library{ .lib = lib, .alloc = alloc, .mutex = mutex };
}
pub fn deinit(self: *Library) void {
self.alloc.destroy(self.mutex);
self.lib.deinit();
}
};
@ -38,7 +53,8 @@ pub const FreetypeLibrary = struct {
pub const NoopLibrary = struct {
pub const InitError = error{};
pub fn init() InitError!Library {
pub fn init(alloc: Allocator) InitError!Library {
_ = alloc;
return Library{};
}

View File

@ -99,7 +99,7 @@ test "SVG" {
const alloc = testing.allocator;
const testFont = font.embedded.julia_mono;
var lib = try font.Library.init();
var lib = try font.Library.init(alloc);
defer lib.deinit();
var face = try font.Face.init(lib, testFont, .{ .size = .{ .points = 12 } });

View File

@ -1761,7 +1761,7 @@ fn testShaperWithFont(alloc: Allocator, font_req: TestFont) !TestShaper {
.nerd_font => font.embedded.nerd_font,
};
var lib = try Library.init();
var lib = try Library.init(alloc);
errdefer lib.deinit();
var c = Collection.init();

View File

@ -1220,7 +1220,7 @@ fn testShaperWithFont(alloc: Allocator, font_req: TestFont) !TestShaper {
.arabic => font.embedded.arabic,
};
var lib = try Library.init();
var lib = try Library.init(alloc);
errdefer lib.deinit();
var c = Collection.init();

View File

@ -279,6 +279,7 @@ pub const Action = union(enum) {
/// Scroll the screen varying amounts.
scroll_to_top,
scroll_to_bottom,
scroll_to_selection,
scroll_page_up,
scroll_page_down,
scroll_page_fractional: f32,
@ -345,7 +346,7 @@ pub const Action = union(enum) {
move_tab: isize,
/// Toggle the tab overview.
/// This only works with libadwaita enabled currently.
/// This only works with libadwaita version 1.4.0 or newer.
toggle_tab_overview,
/// Change the title of the current focused surface via a prompt.
@ -567,6 +568,8 @@ pub const Action = union(enum) {
left,
up,
auto, // splits along the larger direction
pub const default: SplitDirection = .auto;
};
pub const SplitFocusDirection = enum {
@ -728,7 +731,28 @@ pub const Action = union(enum) {
Action.CursorKey => return Error.InvalidAction,
else => {
const idx = colonIdx orelse return Error.InvalidFormat;
// Get the parameter after the colon. The parameter
// can be optional for action types that can have a
// "default" decl.
const idx = colonIdx orelse {
switch (@typeInfo(field.type)) {
.@"struct",
.@"union",
.@"enum",
=> if (@hasDecl(field.type, "default")) {
return @unionInit(
Action,
field.name,
@field(field.type, "default"),
);
},
else => {},
}
return Error.InvalidFormat;
};
const param = input[idx + 1 ..];
return @unionInit(
Action,
@ -789,6 +813,7 @@ pub const Action = union(enum) {
.select_all,
.scroll_to_top,
.scroll_to_bottom,
.scroll_to_selection,
.scroll_page_up,
.scroll_page_down,
.scroll_page_fractional,
@ -2013,6 +2038,17 @@ test "parse: action with enum" {
}
}
test "parse: action with enum with default" {
const testing = std.testing;
// parameter
{
const binding = try parseSingle("a=new_split");
try testing.expect(binding.action == .new_split);
try testing.expectEqual(Action.SplitDirection.auto, binding.action.new_split);
}
}
test "parse: action with int" {
const testing = std.testing;

View File

@ -170,6 +170,12 @@ fn actionCommands(action: Action.Key) []const Command {
.description = "Scroll to the bottom of the screen.",
}},
.scroll_to_selection => comptime &.{.{
.action = .scroll_to_selection,
.title = "Scroll to Selection",
.description = "Scroll to the selected text.",
}},
.scroll_page_up => comptime &.{.{
.action = .scroll_page_up,
.title = "Scroll Page Up",

View File

@ -401,7 +401,8 @@ pub const Key = enum(c_int) {
kp_delete,
kp_begin,
// TODO: media keys
// special keys
context_menu,
// modifiers
left_shift,
@ -579,6 +580,7 @@ pub const Key = enum(c_int) {
.backspace => cimgui.c.ImGuiKey_Backspace,
.print_screen => cimgui.c.ImGuiKey_PrintScreen,
.pause => cimgui.c.ImGuiKey_Pause,
.context_menu => cimgui.c.ImGuiKey_Menu,
.f1 => cimgui.c.ImGuiKey_F1,
.f2 => cimgui.c.ImGuiKey_F2,

View File

@ -153,6 +153,7 @@ const code_to_key = code_to_key: {
.{ "Numpad0", .kp_0 },
.{ "NumpadDecimal", .kp_decimal },
.{ "NumpadEqual", .kp_equal },
.{ "ContextMenu", .context_menu },
.{ "ControlLeft", .left_control },
.{ "ShiftLeft", .left_shift },
.{ "AltLeft", .left_alt },

View File

@ -444,7 +444,7 @@ pub const FlatpakHostCommand = struct {
_: [*c]const u8,
params: ?*c.GVariant,
ud: ?*anyopaque,
) callconv(.C) void {
) callconv(.c) void {
const self = @as(*FlatpakHostCommand, @ptrCast(@alignCast(ud)));
const state = state: {
self.state_mutex.lock();

View File

@ -1524,7 +1524,7 @@ const CompletionBlock = objc.Block(struct { self: *Metal }, .{
fn bufferCompleted(
block: *const CompletionBlock.Context,
buffer_id: objc.c.id,
) callconv(.C) void {
) callconv(.c) void {
const self = block.self;
const buffer = objc.Object.fromId(buffer_id);

View File

@ -250,7 +250,7 @@ fn spvCross(
// It would be better to get this out into an output parameter to
// show users but for now we can just log it.
c.spvc_context_set_error_callback(ctx, @ptrCast(&(struct {
fn callback(_: ?*anyopaque, msg_ptr: [*c]const u8) callconv(.C) void {
fn callback(_: ?*anyopaque, msg_ptr: [*c]const u8) callconv(.c) void {
const msg = std.mem.sliceTo(msg_ptr, 0);
std.log.warn("spirv-cross error message={s}", .{msg});
}

View File

@ -745,7 +745,7 @@ const Subprocess = struct {
});
arena: std.heap.ArenaAllocator,
cwd: ?[]const u8,
cwd: ?[:0]const u8,
env: ?EnvMap,
args: []const [:0]const u8,
grid_size: renderer.GridSize,
@ -985,8 +985,8 @@ const Subprocess = struct {
// We have to copy the cwd because there is no guarantee that
// pointers in full_config remain valid.
const cwd: ?[]u8 = if (cfg.working_directory) |cwd|
try alloc.dupe(u8, cwd)
const cwd: ?[:0]u8 = if (cfg.working_directory) |cwd|
try alloc.dupeZ(u8, cwd)
else
null;
@ -1048,6 +1048,47 @@ const Subprocess = struct {
log.debug("starting command command={s}", .{self.args});
// If we can't access the cwd, then don't set any cwd and inherit.
// This is important because our cwd can be set by the shell (OSC 7)
// and we don't want to break new windows.
const cwd: ?[:0]const u8 = if (self.cwd) |proposed| cwd: {
if ((comptime build_config.flatpak) and internal_os.isFlatpak()) {
// Flatpak sandboxing prevents access to certain reserved paths
// regardless of configured permissions. Perform a test spawn
// to get around this problem
//
// https://docs.flatpak.org/en/latest/sandbox-permissions.html#reserved-paths
log.info("flatpak detected, will use host command to verify cwd access", .{});
const dev_null = try std.fs.cwd().openFile("/dev/null", .{ .mode = .read_write });
defer dev_null.close();
var cmd: internal_os.FlatpakHostCommand = .{
.argv = &[_][]const u8{
"/bin/sh",
"-c",
":",
},
.cwd = proposed,
.stdin = dev_null.handle,
.stdout = dev_null.handle,
.stderr = dev_null.handle,
};
_ = cmd.spawn(alloc) catch |err| {
log.warn("cannot spawn command at cwd, ignoring: {}", .{err});
break :cwd null;
};
_ = try cmd.wait();
break :cwd proposed;
}
if (std.fs.cwd().access(proposed, .{})) {
break :cwd proposed;
} else |err| {
log.warn("cannot access cwd, ignoring: {}", .{err});
break :cwd null;
}
} else null;
// In flatpak, we use the HostCommand to execute our shell.
if (internal_os.isFlatpak()) flatpak: {
if (comptime !build_config.flatpak) {
@ -1058,6 +1099,7 @@ const Subprocess = struct {
// Flatpak command must have a stable pointer.
self.flatpak_command = .{
.argv = self.args,
.cwd = cwd,
.env = if (self.env) |*env| env else null,
.stdin = pty.slave,
.stdout = pty.slave,
@ -1083,18 +1125,6 @@ const Subprocess = struct {
};
}
// If we can't access the cwd, then don't set any cwd and inherit.
// This is important because our cwd can be set by the shell (OSC 7)
// and we don't want to break new windows.
const cwd: ?[]const u8 = if (self.cwd) |proposed| cwd: {
if (std.fs.cwd().access(proposed, .{})) {
break :cwd proposed;
} else |err| {
log.warn("cannot access cwd, ignoring: {}", .{err});
break :cwd null;
}
} else null;
// Build our subcommand
var cmd: Command = .{
.path = self.args[0],

View File

@ -239,7 +239,7 @@ fn setupBash(
resource_dir: []const u8,
env: *EnvMap,
) !?config.Command {
var args = try std.ArrayList([:0]const u8).initCapacity(alloc, 2);
var args = try std.ArrayList([:0]const u8).initCapacity(alloc, 3);
defer args.deinit();
// Iterator that yields each argument in the original command line.
@ -247,12 +247,17 @@ fn setupBash(
var iter = try command.argIterator(alloc);
defer iter.deinit();
// Start accumulating arguments with the executable and `--posix` mode flag.
// Start accumulating arguments with the executable and initial flags.
if (iter.next()) |exe| {
try args.append(try alloc.dupeZ(u8, exe));
} else return null;
try args.append("--posix");
// On macOS, we request a login shell to match that platform's norms.
if (comptime builtin.target.os.tag.isDarwin()) {
try args.append("--login");
}
// Stores the list of intercepted command line flags that will be passed
// to our shell integration script: --norc --noprofile
// We always include at least "1" so the script can differentiate between
@ -342,9 +347,12 @@ test "bash" {
const command = try setupBash(alloc, .{ .shell = "bash" }, ".", &env);
try testing.expectEqual(2, command.?.direct.len);
try testing.expect(command.?.direct.len >= 2);
try testing.expectEqualStrings("bash", command.?.direct[0]);
try testing.expectEqualStrings("--posix", command.?.direct[1]);
if (comptime builtin.target.os.tag.isDarwin()) {
try testing.expectEqualStrings("--login", command.?.direct[2]);
}
try testing.expectEqualStrings("./shell-integration/bash/ghostty.bash", env.get("ENV").?);
try testing.expectEqualStrings("1", env.get("GHOSTTY_BASH_INJECT").?);
}
@ -387,9 +395,12 @@ test "bash: inject flags" {
const command = try setupBash(alloc, .{ .shell = "bash --norc" }, ".", &env);
try testing.expectEqual(2, command.?.direct.len);
try testing.expect(command.?.direct.len >= 2);
try testing.expectEqualStrings("bash", command.?.direct[0]);
try testing.expectEqualStrings("--posix", command.?.direct[1]);
if (comptime builtin.target.os.tag.isDarwin()) {
try testing.expectEqualStrings("--login", command.?.direct[2]);
}
try testing.expectEqualStrings("1 --norc", env.get("GHOSTTY_BASH_INJECT").?);
}
@ -400,9 +411,12 @@ test "bash: inject flags" {
const command = try setupBash(alloc, .{ .shell = "bash --noprofile" }, ".", &env);
try testing.expectEqual(2, command.?.direct.len);
try testing.expect(command.?.direct.len >= 2);
try testing.expectEqualStrings("bash", command.?.direct[0]);
try testing.expectEqualStrings("--posix", command.?.direct[1]);
if (comptime builtin.target.os.tag.isDarwin()) {
try testing.expectEqualStrings("--login", command.?.direct[2]);
}
try testing.expectEqualStrings("1 --noprofile", env.get("GHOSTTY_BASH_INJECT").?);
}
}
@ -419,18 +433,24 @@ test "bash: rcfile" {
// bash --rcfile
{
const command = try setupBash(alloc, .{ .shell = "bash --rcfile profile.sh" }, ".", &env);
try testing.expectEqual(2, command.?.direct.len);
try testing.expect(command.?.direct.len >= 2);
try testing.expectEqualStrings("bash", command.?.direct[0]);
try testing.expectEqualStrings("--posix", command.?.direct[1]);
if (comptime builtin.target.os.tag.isDarwin()) {
try testing.expectEqualStrings("--login", command.?.direct[2]);
}
try testing.expectEqualStrings("profile.sh", env.get("GHOSTTY_BASH_RCFILE").?);
}
// bash --init-file
{
const command = try setupBash(alloc, .{ .shell = "bash --init-file profile.sh" }, ".", &env);
try testing.expectEqual(2, command.?.direct.len);
try testing.expect(command.?.direct.len >= 2);
try testing.expectEqualStrings("bash", command.?.direct[0]);
try testing.expectEqualStrings("--posix", command.?.direct[1]);
if (comptime builtin.target.os.tag.isDarwin()) {
try testing.expectEqualStrings("--login", command.?.direct[2]);
}
try testing.expectEqualStrings("profile.sh", env.get("GHOSTTY_BASH_RCFILE").?);
}
}
@ -476,25 +496,35 @@ test "bash: additional arguments" {
// "-" argument separator
{
const command = try setupBash(alloc, .{ .shell = "bash - --arg file1 file2" }, ".", &env);
try testing.expectEqual(6, command.?.direct.len);
try testing.expect(command.?.direct.len >= 6);
try testing.expectEqualStrings("bash", command.?.direct[0]);
try testing.expectEqualStrings("--posix", command.?.direct[1]);
try testing.expectEqualStrings("-", command.?.direct[2]);
try testing.expectEqualStrings("--arg", command.?.direct[3]);
try testing.expectEqualStrings("file1", command.?.direct[4]);
try testing.expectEqualStrings("file2", command.?.direct[5]);
if (comptime builtin.target.os.tag.isDarwin()) {
try testing.expectEqualStrings("--login", command.?.direct[2]);
}
const offset = if (comptime builtin.target.os.tag.isDarwin()) 3 else 2;
try testing.expectEqualStrings("-", command.?.direct[offset + 0]);
try testing.expectEqualStrings("--arg", command.?.direct[offset + 1]);
try testing.expectEqualStrings("file1", command.?.direct[offset + 2]);
try testing.expectEqualStrings("file2", command.?.direct[offset + 3]);
}
// "--" argument separator
{
const command = try setupBash(alloc, .{ .shell = "bash -- --arg file1 file2" }, ".", &env);
try testing.expectEqual(6, command.?.direct.len);
try testing.expect(command.?.direct.len >= 6);
try testing.expectEqualStrings("bash", command.?.direct[0]);
try testing.expectEqualStrings("--posix", command.?.direct[1]);
try testing.expectEqualStrings("--", command.?.direct[2]);
try testing.expectEqualStrings("--arg", command.?.direct[3]);
try testing.expectEqualStrings("file1", command.?.direct[4]);
try testing.expectEqualStrings("file2", command.?.direct[5]);
if (comptime builtin.target.os.tag.isDarwin()) {
try testing.expectEqualStrings("--login", command.?.direct[2]);
}
const offset = if (comptime builtin.target.os.tag.isDarwin()) 3 else 2;
try testing.expectEqualStrings("--", command.?.direct[offset + 0]);
try testing.expectEqualStrings("--arg", command.?.direct[offset + 1]);
try testing.expectEqualStrings("file1", command.?.direct[offset + 2]);
try testing.expectEqualStrings("file2", command.?.direct[offset + 3]);
}
}