Set an initial start position (#3929)

Allow the ability to set an initial start position from the config. Adds
`window-initial-position-{x,y}` to the config as an optional i16 value
(see swift docs in [this
comment](https://github.com/ghostty-org/ghostty/pull/3929#discussion_r1899266607)
for the reasoning behind this if needed) and handles setting the
position when the initial window is created

Closes https://github.com/ghostty-org/ghostty/issues/3362
This commit is contained in:
Mitchell Hashimoto
2025-01-02 13:35:13 -08:00
committed by GitHub
6 changed files with 92 additions and 3 deletions

View File

@ -273,6 +273,28 @@ class TerminalController: BaseTerminalController {
}
}
private func setInitialWindowPosition(x: Int16?, y: Int16?, windowDecorations: Bool) {
guard let window else { return }
// If we don't have both an X and Y we center.
guard let x, let y else {
window.center()
return
}
// Prefer the screen our window is being placed on otherwise our primary screen.
guard let screen = window.screen ?? NSScreen.screens.first else {
window.center()
return
}
// Orient based on the top left of the primary monitor
let frame = screen.visibleFrame
window.setFrameOrigin(.init(
x: frame.minX + CGFloat(x),
y: frame.maxY - (CGFloat(y) + window.frame.height)))
}
//MARK: - NSWindowController
override func windowWillLoad() {
@ -368,9 +390,12 @@ 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(
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 {

View File

@ -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 "" }

View File

@ -510,6 +510,13 @@ pub const Surface = struct {
) orelse return glfw.mustGetErrorCode();
errdefer win.destroy();
// Setup our
setInitialWindowPosition(
win,
app.config.@"window-position-x",
app.config.@"window-position-y",
);
// Get our physical DPI - debug only because we don't have a use for
// this but the logging of it may be useful
if (builtin.mode == .Debug) {
@ -663,6 +670,17 @@ pub const Surface = struct {
});
}
/// Set the initial window position. This is called exactly once at
/// surface initialization time. This may be called before "self"
/// is fully initialized.
fn setInitialWindowPosition(win: glfw.Window, x: ?i16, y: ?i16) void {
const start_position_x = x orelse return;
const start_position_y = y orelse return;
log.debug("setting initial window position ({},{})", .{ start_position_x, start_position_y });
win.setPos(.{ .x = start_position_x, .y = start_position_y });
}
/// Set the size limits of the window.
/// Note: this interface is not good, we should redo it if we plan
/// to use this more. i.e. you can't set max width but no max height,

View File

@ -786,6 +786,7 @@ fn setInitialSize(
),
}
}
fn showDesktopNotification(
self: *App,
target: apprt.Target,

View File

@ -1108,6 +1108,32 @@ keybind: Keybinds = .{},
@"window-height": u32 = 0,
@"window-width": u32 = 0,
/// The starting window position. This position is in pixels and is relative
/// to the top-left corner of the primary monitor. Both values must be set to take
/// effect. If only one value is set, it is ignored.
///
/// Note that the window manager may put limits on the position or override
/// the position. For example, a tiling window manager may force the window
/// to be a certain position to fit within the grid. There is nothing Ghostty
/// will do about this, but it will make an effort.
///
/// Also note that negative values are also up to the operating system and
/// window manager. Some window managers may not allow windows to be placed
/// off-screen.
///
/// Invalid positions are runtime-specific, but generally the positions are
/// clamped to the nearest valid position.
///
/// On macOS, the window position is relative to the top-left corner of
/// the visible screen area. This means that if the menu bar is visible, the
/// window will be placed below the menu bar.
///
/// Note: this is only supported on macOS and Linux GLFW builds. The GTK
/// runtime does not support setting the window position (this is a limitation
/// of GTK 4.0).
@"window-position-x": ?i16 = null,
@"window-position-y": ?i16 = null,
/// Whether to enable saving and restoring window state. Window state includes
/// their position, size, tabs, splits, etc. Some window state requires shell
/// integration, such as preserving working directories. See `shell-integration`

View File

@ -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);