diff --git a/macos/Sources/Features/Terminal/TerminalController.swift b/macos/Sources/Features/Terminal/TerminalController.swift index 331f26c97..873bbe7cc 100644 --- a/macos/Sources/Features/Terminal/TerminalController.swift +++ b/macos/Sources/Features/Terminal/TerminalController.swift @@ -368,10 +368,10 @@ class TerminalController: BaseTerminalController { } } - // Center the window to start, we'll move the window frame automatically - // when cascading. - window.center() - + // Set our window positioning to coordinates if config value exists, otherwise + // fallback to original centering behavior + setInitialWindowPosition(window, x: config.windowPositionX, y: config.windowPositionY, windowDecorations: config.windowDecorations) + // Make sure our theme is set on the window so styling is correct. if let windowTheme = config.windowTheme { window.windowTheme = .init(rawValue: windowTheme) @@ -468,6 +468,31 @@ class TerminalController: BaseTerminalController { let data = TerminalRestorableState(from: self) data.encode(with: state) } + + func setInitialWindowPosition(_ window: NSWindow, x: Int16?, y: Int16?, windowDecorations: Bool) { + if let primaryScreen = NSScreen.screens.first { + let frame = primaryScreen.visibleFrame + + if let windowPositionX = x, let windowPositionY = y { + // Offset titlebar if needed, otherwise use default padding of 12 + // NOTE: Not 100% certain where this extra padding comes from but I'd love + // to calculate it dynamically if possible + let titlebarHeight = windowDecorations ? window.frame.height - (window.contentView?.frame.height ?? 0) : 12 + + // Orient based on the top left of the primary monitor + let startPositionX = frame.origin.x + CGFloat(windowPositionX) + let startPositionY = (frame.origin.y + frame.height) - (CGFloat(windowPositionY) + window.frame.height) + titlebarHeight + + window.setFrameOrigin(NSPoint(x: startPositionX, y: startPositionY)) + } else { + // Fallback to original centering behavior + window.center() + } + } else { + // Fallback to original centering behavior + window.center() + } + } // MARK: First Responder diff --git a/macos/Sources/Ghostty/Ghostty.App.swift b/macos/Sources/Ghostty/Ghostty.App.swift index 2d679122a..2d9822d6e 100644 --- a/macos/Sources/Ghostty/Ghostty.App.swift +++ b/macos/Sources/Ghostty/Ghostty.App.swift @@ -517,9 +517,6 @@ extension Ghostty { case GHOSTTY_ACTION_INITIAL_SIZE: setInitialSize(app, target: target, v: action.action.initial_size) - case GHOSTTY_ACTION_INITIAL_POSITION: - setInitialPosition(app, target: target, v: action.action.initial_position) - case GHOSTTY_ACTION_CELL_SIZE: setCellSize(app, target: target, v: action.action.cell_size) @@ -1072,26 +1069,6 @@ extension Ghostty { } } - private static func setInitialPosition( - _ app: ghostty_app_t, - target: ghostty_target_s, - v: ghostty_action_initial_position_s) { - switch (target.tag) { - case GHOSTTY_TARGET_APP: - Ghostty.logger.warning("mouse over link 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 } - surfaceView.initialPosition = NSMakePoint(Double(v.x), Double(v.y)) - - - default: - assertionFailure() - } - } - private static func setCellSize( _ app: ghostty_app_t, target: ghostty_target_s, diff --git a/macos/Sources/Ghostty/Ghostty.Config.swift b/macos/Sources/Ghostty/Ghostty.Config.swift index 1e733c5e1..82d17a2de 100644 --- a/macos/Sources/Ghostty/Ghostty.Config.swift +++ b/macos/Sources/Ghostty/Ghostty.Config.swift @@ -149,6 +149,20 @@ extension Ghostty { guard let ptr = v else { return "" } return String(cString: ptr) } + + var windowPositionX: Int16? { + guard let config = self.config else { return nil } + var v: Int16 = 0 + let key = "window-position-x" + return ghostty_config_get(config, &v, key, UInt(key.count)) ? v : nil + } + + var windowPositionY: Int16? { + guard let config = self.config else { return nil } + var v: Int16 = 0 + let key = "window-position-y" + return ghostty_config_get(config, &v, key, UInt(key.count)) ? v : nil + } var windowNewTabPosition: String { guard let config = self.config else { return "" } diff --git a/src/config/c_get.zig b/src/config/c_get.zig index dd7c7cce8..d3f38415e 100644 --- a/src/config/c_get.zig +++ b/src/config/c_get.zig @@ -42,6 +42,11 @@ fn getValue(ptr_raw: *anyopaque, value: anytype) bool { ptr.* = @intCast(value); }, + i16 => { + const ptr: *c_short = @ptrCast(@alignCast(ptr_raw)); + ptr.* = @intCast(value); + }, + f32, f64 => |Float| { const ptr: *Float = @ptrCast(@alignCast(ptr_raw)); ptr.* = @floatCast(value);