From 5ced72498e122bcdf0b5c390f72a389ddbf2f1f5 Mon Sep 17 00:00:00 2001 From: Adam Wolf Date: Sun, 29 Dec 2024 11:23:54 -0600 Subject: [PATCH 01/11] feat(linux): allow setting an intial start position --- include/ghostty.h | 1 + src/Surface.zig | 10 ++++++++++ src/apprt/action.zig | 11 +++++++++++ src/apprt/glfw.zig | 17 +++++++++++++++++ src/apprt/gtk/App.zig | 15 +++++++++++++++ src/apprt/gtk/Surface.zig | 6 ++++++ src/config/Config.zig | 13 +++++++++++++ 7 files changed, 73 insertions(+) diff --git a/include/ghostty.h b/include/ghostty.h index 4b8d409e9..dc147de0d 100644 --- a/include/ghostty.h +++ b/include/ghostty.h @@ -577,6 +577,7 @@ typedef enum { GHOSTTY_ACTION_PRESENT_TERMINAL, GHOSTTY_ACTION_SIZE_LIMIT, GHOSTTY_ACTION_INITIAL_SIZE, + GHOSTTY_ACTION_INITIAL_POSITION, GHOSTTY_ACTION_CELL_SIZE, GHOSTTY_ACTION_INSPECTOR, GHOSTTY_ACTION_RENDER_INSPECTOR, diff --git a/src/Surface.zig b/src/Surface.zig index c359efd8a..ace392bb3 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -621,6 +621,8 @@ pub fn init( const width = @max(config.@"window-width" * cell_size.width, 640); const width_f32: f32 = @floatFromInt(width); const height_f32: f32 = @floatFromInt(height); + const position_x = config.@"window-position-x"; + const position_y = config.@"window-position-y"; // The final values are affected by content scale and we need to // account for the padding so we get the exact correct grid size. @@ -642,6 +644,14 @@ pub fn init( // an initial size shouldn't stop our terminal from working. log.warn("unable to set initial window size: {s}", .{err}); }; + + rt_app.performAction( + .{ .surface = self }, + .initial_position, + .{ .x = position_x, .y = position_y }, + ) catch |err| { + log.warn("unable to set initial window position: {s}", .{err}); + }; } if (config.title) |title| { diff --git a/src/apprt/action.zig b/src/apprt/action.zig index de6758d6c..35ad3c0ce 100644 --- a/src/apprt/action.zig +++ b/src/apprt/action.zig @@ -136,6 +136,11 @@ pub const Action = union(Key) { /// after the surface is initialized it should be ignored. initial_size: InitialSize, + // Specifies the initial position of the target terminal. This will be + // sent only during the initialization of a surface. If it is received + // after the surface is initialized it should be ignored. + initial_position: InitialPosition, + /// The cell size has changed to the given dimensions in pixels. cell_size: CellSize, @@ -237,6 +242,7 @@ pub const Action = union(Key) { present_terminal, size_limit, initial_size, + initial_position, cell_size, inspector, render_inspector, @@ -427,6 +433,11 @@ pub const InitialSize = extern struct { height: u32, }; +pub const InitialPosition = extern struct { + x: i32, + y: i32, +}; + pub const CellSize = extern struct { width: u32, height: u32, diff --git a/src/apprt/glfw.zig b/src/apprt/glfw.zig index 64b0cbe81..a17b18a29 100644 --- a/src/apprt/glfw.zig +++ b/src/apprt/glfw.zig @@ -178,6 +178,14 @@ pub const App = struct { ), }, + .initial_position => switch (target) { + .app => {}, + .surface => |surface| surface.rt_surface.setInitialWindowPosition( + value.x, + value.y, + ), + }, + .toggle_fullscreen => self.toggleFullscreen(target), .open_config => try configpkg.edit.open(self.app.alloc), @@ -663,6 +671,15 @@ 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(self: *const Surface, x: i32, y: i32) void { + log.debug("setting initial window position ({},{})", .{ x, y }); + + self.window.setPos(.{ .x = x, .y = 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, diff --git a/src/apprt/gtk/App.zig b/src/apprt/gtk/App.zig index c10ba7993..24e1f4346 100644 --- a/src/apprt/gtk/App.zig +++ b/src/apprt/gtk/App.zig @@ -786,6 +786,21 @@ fn setInitialSize( ), } } + +fn setInitialPosition( + _: *App, + target: apprt.Target, + value: apprt.action.InitialPosition, +) void { + switch (target) { + .app => {}, + .surface => |v| v.rt_surface.setInitialWindowPosition( + value.x, + value.y, + ), + } +} + fn showDesktopNotification( self: *App, target: apprt.Target, diff --git a/src/apprt/gtk/Surface.zig b/src/apprt/gtk/Surface.zig index 079cdbd81..546d5ac33 100644 --- a/src/apprt/gtk/Surface.zig +++ b/src/apprt/gtk/Surface.zig @@ -840,6 +840,12 @@ pub fn setInitialWindowSize(self: *const Surface, width: u32, height: u32) !void ); } +pub fn setInitialWindowPosition(self: *const Surface, x: i32, y: i32) !void { + // We need the surface's window to set the position. + const window = self.container.window() orelse return; + c.gtk_window_move(@ptrCast(window.window), x, y); +} + pub fn grabFocus(self: *Surface) void { if (self.container.tab()) |tab| { // If any other surface was focused and zoomed in, set it to non zoomed in diff --git a/src/config/Config.zig b/src/config/Config.zig index 64fea91eb..13a25ad5a 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -1108,6 +1108,19 @@ keybind: Keybinds = .{}, @"window-height": u32 = 0, @"window-width": u32 = 0, +/// The initial window position. This position is in pixels and is relative +/// to the top-left corner of the screen. 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. +/// +/// This will default to the top-left corner of the screen if not set (0, 0). +@"window-position-x": i32 = 0, +@"window-position-y": i32 = 0, + /// 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` From 9a58de6d5a5af905e6bf0347d68e9722f77f85f2 Mon Sep 17 00:00:00 2001 From: Adam Wolf Date: Sun, 29 Dec 2024 11:49:08 -0600 Subject: [PATCH 02/11] feat(macos): allow setting an intial start position --- include/ghostty.h | 7 +++++++ macos/Sources/Ghostty/Ghostty.App.swift | 23 +++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/include/ghostty.h b/include/ghostty.h index dc147de0d..6dab5c27d 100644 --- a/include/ghostty.h +++ b/include/ghostty.h @@ -514,6 +514,12 @@ typedef struct { uint32_t height; } ghostty_action_initial_size_s; +// apprt.action.InitialPosition +typedef struct { + int32_t x; + int32_t y; +} ghostty_action_initial_position_s; + // apprt.action.CellSize typedef struct { uint32_t width; @@ -606,6 +612,7 @@ typedef union { ghostty_action_resize_split_s resize_split; ghostty_action_size_limit_s size_limit; ghostty_action_initial_size_s initial_size; + ghostty_action_initial_position_s initial_position; ghostty_action_cell_size_s cell_size; ghostty_action_inspector_e inspector; ghostty_action_desktop_notification_s desktop_notification; diff --git a/macos/Sources/Ghostty/Ghostty.App.swift b/macos/Sources/Ghostty/Ghostty.App.swift index 2d9822d6e..2d679122a 100644 --- a/macos/Sources/Ghostty/Ghostty.App.swift +++ b/macos/Sources/Ghostty/Ghostty.App.swift @@ -517,6 +517,9 @@ 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) @@ -1069,6 +1072,26 @@ 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, From 7195bda7a24cc7abbb19ffeccce56d2ad1bc0a8d Mon Sep 17 00:00:00 2001 From: Adam Wolf Date: Sun, 29 Dec 2024 13:08:34 -0600 Subject: [PATCH 03/11] chore: add missing case in switch statement --- src/apprt/glfw.zig | 4 ++-- src/apprt/gtk/App.zig | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/apprt/glfw.zig b/src/apprt/glfw.zig index a17b18a29..8f5519a56 100644 --- a/src/apprt/glfw.zig +++ b/src/apprt/glfw.zig @@ -180,7 +180,7 @@ pub const App = struct { .initial_position => switch (target) { .app => {}, - .surface => |surface| surface.rt_surface.setInitialWindowPosition( + .surface => |surface| try surface.rt_surface.setInitialWindowPosition( value.x, value.y, ), @@ -674,7 +674,7 @@ 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(self: *const Surface, x: i32, y: i32) void { + fn setInitialWindowPosition(self: *const Surface, x: i32, y: i32) !void { log.debug("setting initial window position ({},{})", .{ x, y }); self.window.setPos(.{ .x = x, .y = y }); diff --git a/src/apprt/gtk/App.zig b/src/apprt/gtk/App.zig index 24e1f4346..c7331968f 100644 --- a/src/apprt/gtk/App.zig +++ b/src/apprt/gtk/App.zig @@ -474,6 +474,7 @@ pub fn performAction( .pwd => try self.setPwd(target, value), .present_terminal => self.presentTerminal(target), .initial_size => try self.setInitialSize(target, value), + .initial_position => self.setInitialPosition(target, value), .mouse_visibility => self.setMouseVisibility(target, value), .mouse_shape => try self.setMouseShape(target, value), .mouse_over_link => self.setMouseOverLink(target, value), @@ -794,7 +795,7 @@ fn setInitialPosition( ) void { switch (target) { .app => {}, - .surface => |v| v.rt_surface.setInitialWindowPosition( + .surface => |v| try v.rt_surface.setInitialWindowPosition( value.x, value.y, ), From 568f1f9d720365ee6e5d7b3c71c85b5f9774ccf9 Mon Sep 17 00:00:00 2001 From: Adam Wolf Date: Sun, 29 Dec 2024 21:33:44 -0600 Subject: [PATCH 04/11] chore: removed setInitialWindowPosition from gtk and renamed window-position-{x,y} to start-position-{x,y} for clarity --- src/Surface.zig | 4 ++-- src/apprt/gtk/App.zig | 15 --------------- src/apprt/gtk/Surface.zig | 6 ------ src/config/Config.zig | 7 +++++-- 4 files changed, 7 insertions(+), 25 deletions(-) diff --git a/src/Surface.zig b/src/Surface.zig index ace392bb3..ccc3ba0ce 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -621,8 +621,8 @@ pub fn init( const width = @max(config.@"window-width" * cell_size.width, 640); const width_f32: f32 = @floatFromInt(width); const height_f32: f32 = @floatFromInt(height); - const position_x = config.@"window-position-x"; - const position_y = config.@"window-position-y"; + const position_x = config.@"start-position-x"; + const position_y = config.@"start-position-y"; // The final values are affected by content scale and we need to // account for the padding so we get the exact correct grid size. diff --git a/src/apprt/gtk/App.zig b/src/apprt/gtk/App.zig index c7331968f..3d05bc2c5 100644 --- a/src/apprt/gtk/App.zig +++ b/src/apprt/gtk/App.zig @@ -474,7 +474,6 @@ pub fn performAction( .pwd => try self.setPwd(target, value), .present_terminal => self.presentTerminal(target), .initial_size => try self.setInitialSize(target, value), - .initial_position => self.setInitialPosition(target, value), .mouse_visibility => self.setMouseVisibility(target, value), .mouse_shape => try self.setMouseShape(target, value), .mouse_over_link => self.setMouseOverLink(target, value), @@ -788,20 +787,6 @@ fn setInitialSize( } } -fn setInitialPosition( - _: *App, - target: apprt.Target, - value: apprt.action.InitialPosition, -) void { - switch (target) { - .app => {}, - .surface => |v| try v.rt_surface.setInitialWindowPosition( - value.x, - value.y, - ), - } -} - fn showDesktopNotification( self: *App, target: apprt.Target, diff --git a/src/apprt/gtk/Surface.zig b/src/apprt/gtk/Surface.zig index 546d5ac33..079cdbd81 100644 --- a/src/apprt/gtk/Surface.zig +++ b/src/apprt/gtk/Surface.zig @@ -840,12 +840,6 @@ pub fn setInitialWindowSize(self: *const Surface, width: u32, height: u32) !void ); } -pub fn setInitialWindowPosition(self: *const Surface, x: i32, y: i32) !void { - // We need the surface's window to set the position. - const window = self.container.window() orelse return; - c.gtk_window_move(@ptrCast(window.window), x, y); -} - pub fn grabFocus(self: *Surface) void { if (self.container.tab()) |tab| { // If any other surface was focused and zoomed in, set it to non zoomed in diff --git a/src/config/Config.zig b/src/config/Config.zig index 13a25ad5a..5ff110cf4 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -1117,9 +1117,12 @@ keybind: Keybinds = .{}, /// to be a certain position to fit within the grid. There is nothing Ghostty /// will do about this, but it will make an effort. /// +/// Important note: Setting this value will only work on macOs and glfw builds +/// on Linux. GTK 4.0 does not support setting the window position. +/// /// This will default to the top-left corner of the screen if not set (0, 0). -@"window-position-x": i32 = 0, -@"window-position-y": i32 = 0, +@"start-position-x": i32 = 0, +@"start-position-y": i32 = 0, /// Whether to enable saving and restoring window state. Window state includes /// their position, size, tabs, splits, etc. Some window state requires shell From a7e3e5915c73228b5dcbed1c42f21b290151dc1e Mon Sep 17 00:00:00 2001 From: Adam Wolf Date: Sun, 29 Dec 2024 21:55:21 -0600 Subject: [PATCH 05/11] docs: fix spelling of macOS --- src/config/Config.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config/Config.zig b/src/config/Config.zig index 5ff110cf4..ad862540a 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -1117,7 +1117,7 @@ keybind: Keybinds = .{}, /// to be a certain position to fit within the grid. There is nothing Ghostty /// will do about this, but it will make an effort. /// -/// Important note: Setting this value will only work on macOs and glfw builds +/// Important note: Setting this value will only work on macOS and glfw builds /// on Linux. GTK 4.0 does not support setting the window position. /// /// This will default to the top-left corner of the screen if not set (0, 0). From 13d935a401004918759072585fd68555b23f68b5 Mon Sep 17 00:00:00 2001 From: Adam Wolf Date: Sun, 29 Dec 2024 21:56:09 -0600 Subject: [PATCH 06/11] revert: renaming of window-position-{x,y} --- src/Surface.zig | 4 ++-- src/config/Config.zig | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Surface.zig b/src/Surface.zig index ccc3ba0ce..ace392bb3 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -621,8 +621,8 @@ pub fn init( const width = @max(config.@"window-width" * cell_size.width, 640); const width_f32: f32 = @floatFromInt(width); const height_f32: f32 = @floatFromInt(height); - const position_x = config.@"start-position-x"; - const position_y = config.@"start-position-y"; + const position_x = config.@"window-position-x"; + const position_y = config.@"window-position-y"; // The final values are affected by content scale and we need to // account for the padding so we get the exact correct grid size. diff --git a/src/config/Config.zig b/src/config/Config.zig index ad862540a..c9cf188b5 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -1121,8 +1121,8 @@ keybind: Keybinds = .{}, /// on Linux. GTK 4.0 does not support setting the window position. /// /// This will default to the top-left corner of the screen if not set (0, 0). -@"start-position-x": i32 = 0, -@"start-position-y": i32 = 0, +@"window-position-x": i32 = 0, +@"window-position-y": i32 = 0, /// Whether to enable saving and restoring window state. Window state includes /// their position, size, tabs, splits, etc. Some window state requires shell From 970e45559b4e0a6677bcc5cf5b77311bd48b8d21 Mon Sep 17 00:00:00 2001 From: Adam Wolf Date: Mon, 30 Dec 2024 07:39:56 -0600 Subject: [PATCH 07/11] apprt/glfw: handle setting initial window position when window is created --- include/ghostty.h | 8 -------- src/Surface.zig | 10 ---------- src/apprt/action.zig | 11 ----------- src/apprt/glfw.zig | 28 +++++++++++++--------------- src/config/Config.zig | 4 ++-- 5 files changed, 15 insertions(+), 46 deletions(-) diff --git a/include/ghostty.h b/include/ghostty.h index 6dab5c27d..4b8d409e9 100644 --- a/include/ghostty.h +++ b/include/ghostty.h @@ -514,12 +514,6 @@ typedef struct { uint32_t height; } ghostty_action_initial_size_s; -// apprt.action.InitialPosition -typedef struct { - int32_t x; - int32_t y; -} ghostty_action_initial_position_s; - // apprt.action.CellSize typedef struct { uint32_t width; @@ -583,7 +577,6 @@ typedef enum { GHOSTTY_ACTION_PRESENT_TERMINAL, GHOSTTY_ACTION_SIZE_LIMIT, GHOSTTY_ACTION_INITIAL_SIZE, - GHOSTTY_ACTION_INITIAL_POSITION, GHOSTTY_ACTION_CELL_SIZE, GHOSTTY_ACTION_INSPECTOR, GHOSTTY_ACTION_RENDER_INSPECTOR, @@ -612,7 +605,6 @@ typedef union { ghostty_action_resize_split_s resize_split; ghostty_action_size_limit_s size_limit; ghostty_action_initial_size_s initial_size; - ghostty_action_initial_position_s initial_position; ghostty_action_cell_size_s cell_size; ghostty_action_inspector_e inspector; ghostty_action_desktop_notification_s desktop_notification; diff --git a/src/Surface.zig b/src/Surface.zig index ace392bb3..c359efd8a 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -621,8 +621,6 @@ pub fn init( const width = @max(config.@"window-width" * cell_size.width, 640); const width_f32: f32 = @floatFromInt(width); const height_f32: f32 = @floatFromInt(height); - const position_x = config.@"window-position-x"; - const position_y = config.@"window-position-y"; // The final values are affected by content scale and we need to // account for the padding so we get the exact correct grid size. @@ -644,14 +642,6 @@ pub fn init( // an initial size shouldn't stop our terminal from working. log.warn("unable to set initial window size: {s}", .{err}); }; - - rt_app.performAction( - .{ .surface = self }, - .initial_position, - .{ .x = position_x, .y = position_y }, - ) catch |err| { - log.warn("unable to set initial window position: {s}", .{err}); - }; } if (config.title) |title| { diff --git a/src/apprt/action.zig b/src/apprt/action.zig index 35ad3c0ce..de6758d6c 100644 --- a/src/apprt/action.zig +++ b/src/apprt/action.zig @@ -136,11 +136,6 @@ pub const Action = union(Key) { /// after the surface is initialized it should be ignored. initial_size: InitialSize, - // Specifies the initial position of the target terminal. This will be - // sent only during the initialization of a surface. If it is received - // after the surface is initialized it should be ignored. - initial_position: InitialPosition, - /// The cell size has changed to the given dimensions in pixels. cell_size: CellSize, @@ -242,7 +237,6 @@ pub const Action = union(Key) { present_terminal, size_limit, initial_size, - initial_position, cell_size, inspector, render_inspector, @@ -433,11 +427,6 @@ pub const InitialSize = extern struct { height: u32, }; -pub const InitialPosition = extern struct { - x: i32, - y: i32, -}; - pub const CellSize = extern struct { width: u32, height: u32, diff --git a/src/apprt/glfw.zig b/src/apprt/glfw.zig index 8f5519a56..3481e4833 100644 --- a/src/apprt/glfw.zig +++ b/src/apprt/glfw.zig @@ -149,10 +149,14 @@ pub const App = struct { value: apprt.Action.Value(action), ) !void { switch (action) { - .new_window => _ = try self.newSurface(switch (target) { - .app => null, - .surface => |v| v, - }), + .new_window => { + var surface = try self.newSurface(switch (target) { + .app => null, + .surface => |v| v, + }); + + try surface.setInitialWindowPosition(self.config.@"window-position-x", self.config.@"window-position-y"); + }, .new_tab => try self.newTab(switch (target) { .app => null, @@ -178,14 +182,6 @@ pub const App = struct { ), }, - .initial_position => switch (target) { - .app => {}, - .surface => |surface| try surface.rt_surface.setInitialWindowPosition( - value.x, - value.y, - ), - }, - .toggle_fullscreen => self.toggleFullscreen(target), .open_config => try configpkg.edit.open(self.app.alloc), @@ -674,10 +670,12 @@ 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(self: *const Surface, x: i32, y: i32) !void { - log.debug("setting initial window position ({},{})", .{ x, y }); + fn setInitialWindowPosition(self: *const Surface, x: ?i16, y: ?i16) !void { + const start_position_x = x orelse return; + const start_position_y = y orelse return; - self.window.setPos(.{ .x = x, .y = y }); + log.debug("setting initial window position ({},{})", .{ start_position_x, start_position_y }); + self.window.setPos(.{ .x = start_position_x, .y = start_position_y }); } /// Set the size limits of the window. diff --git a/src/config/Config.zig b/src/config/Config.zig index c9cf188b5..7fba7ddd9 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -1121,8 +1121,8 @@ keybind: Keybinds = .{}, /// on Linux. GTK 4.0 does not support setting the window position. /// /// This will default to the top-left corner of the screen if not set (0, 0). -@"window-position-x": i32 = 0, -@"window-position-y": i32 = 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 From 200d0d642be5a10fd9de0de0ddb4583b8deabd59 Mon Sep 17 00:00:00 2001 From: Adam Wolf Date: Tue, 31 Dec 2024 00:50:07 -0600 Subject: [PATCH 08/11] macos: handle setting initial window position when window is created --- .../Terminal/TerminalController.swift | 33 ++++++++++++++++--- macos/Sources/Ghostty/Ghostty.App.swift | 23 ------------- macos/Sources/Ghostty/Ghostty.Config.swift | 14 ++++++++ src/config/c_get.zig | 5 +++ 4 files changed, 48 insertions(+), 27 deletions(-) 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); From 16bf3b8820c8e1948740c46405dfe2ef02e58517 Mon Sep 17 00:00:00 2001 From: Adam Wolf Date: Tue, 31 Dec 2024 00:51:13 -0600 Subject: [PATCH 09/11] docs: update config docs to reflect window positioning changes --- src/config/Config.zig | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/config/Config.zig b/src/config/Config.zig index 7fba7ddd9..93f107bb2 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -1109,7 +1109,7 @@ keybind: Keybinds = .{}, @"window-width": u32 = 0, /// The initial window position. This position is in pixels and is relative -/// to the top-left corner of the screen. Both values must be set to take +/// 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 @@ -1119,8 +1119,6 @@ keybind: Keybinds = .{}, /// /// Important note: Setting this value will only work on macOS and glfw builds /// on Linux. GTK 4.0 does not support setting the window position. -/// -/// This will default to the top-left corner of the screen if not set (0, 0). @"window-position-x": ?i16 = null, @"window-position-y": ?i16 = null, From f9250e28b52c779d154fbf13db74b418e74c172c Mon Sep 17 00:00:00 2001 From: Adam Wolf Date: Tue, 31 Dec 2024 10:35:23 -0600 Subject: [PATCH 10/11] chore: rename window-position-{x,y} to window-initial-position-{x,y} --- macos/Sources/Features/Terminal/TerminalController.swift | 2 +- macos/Sources/Ghostty/Ghostty.Config.swift | 8 ++++---- src/apprt/glfw.zig | 2 +- src/config/Config.zig | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/macos/Sources/Features/Terminal/TerminalController.swift b/macos/Sources/Features/Terminal/TerminalController.swift index 873bbe7cc..e3e5ac3f6 100644 --- a/macos/Sources/Features/Terminal/TerminalController.swift +++ b/macos/Sources/Features/Terminal/TerminalController.swift @@ -370,7 +370,7 @@ class TerminalController: BaseTerminalController { // 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) + setInitialWindowPosition(window, x: config.windowInitialPositionX, y: config.windowInitialPositionY, windowDecorations: config.windowDecorations) // Make sure our theme is set on the window so styling is correct. if let windowTheme = config.windowTheme { diff --git a/macos/Sources/Ghostty/Ghostty.Config.swift b/macos/Sources/Ghostty/Ghostty.Config.swift index 82d17a2de..b6ae5de96 100644 --- a/macos/Sources/Ghostty/Ghostty.Config.swift +++ b/macos/Sources/Ghostty/Ghostty.Config.swift @@ -150,17 +150,17 @@ extension Ghostty { return String(cString: ptr) } - var windowPositionX: Int16? { + var windowInitialPositionX: Int16? { guard let config = self.config else { return nil } var v: Int16 = 0 - let key = "window-position-x" + let key = "window-initial-position-x" return ghostty_config_get(config, &v, key, UInt(key.count)) ? v : nil } - var windowPositionY: Int16? { + var windowInitialPositionY: Int16? { guard let config = self.config else { return nil } var v: Int16 = 0 - let key = "window-position-y" + let key = "window-initial-position-y" return ghostty_config_get(config, &v, key, UInt(key.count)) ? v : nil } diff --git a/src/apprt/glfw.zig b/src/apprt/glfw.zig index 3481e4833..5f48e0dd7 100644 --- a/src/apprt/glfw.zig +++ b/src/apprt/glfw.zig @@ -155,7 +155,7 @@ pub const App = struct { .surface => |v| v, }); - try surface.setInitialWindowPosition(self.config.@"window-position-x", self.config.@"window-position-y"); + try surface.setInitialWindowPosition(self.config.@"window-initial-position-x", self.config.@"window-initial-position-y"); }, .new_tab => try self.newTab(switch (target) { diff --git a/src/config/Config.zig b/src/config/Config.zig index 93f107bb2..7d8d648df 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -1119,8 +1119,8 @@ keybind: Keybinds = .{}, /// /// Important note: Setting this value will only work on macOS and glfw builds /// on Linux. GTK 4.0 does not support setting the window position. -@"window-position-x": ?i16 = null, -@"window-position-y": ?i16 = null, +@"window-initial-position-x": ?i16 = null, +@"window-initial-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 From 29b96be84fb3e1498172e02cdc42a481b4e2e998 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 2 Jan 2025 13:04:30 -0800 Subject: [PATCH 11/11] tweaks to window position --- .../Terminal/TerminalController.swift | 54 +++++++++---------- macos/Sources/Ghostty/Ghostty.Config.swift | 8 +-- src/apprt/glfw.zig | 23 ++++---- src/config/Config.zig | 22 ++++++-- 4 files changed, 61 insertions(+), 46 deletions(-) diff --git a/macos/Sources/Features/Terminal/TerminalController.swift b/macos/Sources/Features/Terminal/TerminalController.swift index e3e5ac3f6..1e8e4c214 100644 --- a/macos/Sources/Features/Terminal/TerminalController.swift +++ b/macos/Sources/Features/Terminal/TerminalController.swift @@ -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() { @@ -370,8 +392,11 @@ class TerminalController: BaseTerminalController { // Set our window positioning to coordinates if config value exists, otherwise // fallback to original centering behavior - setInitialWindowPosition(window, x: config.windowInitialPositionX, y: config.windowInitialPositionY, windowDecorations: config.windowDecorations) - + 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 { window.windowTheme = .init(rawValue: windowTheme) @@ -468,31 +493,6 @@ 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.Config.swift b/macos/Sources/Ghostty/Ghostty.Config.swift index b6ae5de96..82d17a2de 100644 --- a/macos/Sources/Ghostty/Ghostty.Config.swift +++ b/macos/Sources/Ghostty/Ghostty.Config.swift @@ -150,17 +150,17 @@ extension Ghostty { return String(cString: ptr) } - var windowInitialPositionX: Int16? { + var windowPositionX: Int16? { guard let config = self.config else { return nil } var v: Int16 = 0 - let key = "window-initial-position-x" + let key = "window-position-x" return ghostty_config_get(config, &v, key, UInt(key.count)) ? v : nil } - var windowInitialPositionY: Int16? { + var windowPositionY: Int16? { guard let config = self.config else { return nil } var v: Int16 = 0 - let key = "window-initial-position-y" + let key = "window-position-y" return ghostty_config_get(config, &v, key, UInt(key.count)) ? v : nil } diff --git a/src/apprt/glfw.zig b/src/apprt/glfw.zig index 5f48e0dd7..3fbef0f22 100644 --- a/src/apprt/glfw.zig +++ b/src/apprt/glfw.zig @@ -149,14 +149,10 @@ pub const App = struct { value: apprt.Action.Value(action), ) !void { switch (action) { - .new_window => { - var surface = try self.newSurface(switch (target) { - .app => null, - .surface => |v| v, - }); - - try surface.setInitialWindowPosition(self.config.@"window-initial-position-x", self.config.@"window-initial-position-y"); - }, + .new_window => _ = try self.newSurface(switch (target) { + .app => null, + .surface => |v| v, + }), .new_tab => try self.newTab(switch (target) { .app => null, @@ -514,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) { @@ -670,12 +673,12 @@ 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(self: *const Surface, x: ?i16, y: ?i16) !void { + 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 }); - self.window.setPos(.{ .x = start_position_x, .y = start_position_y }); + win.setPos(.{ .x = start_position_x, .y = start_position_y }); } /// Set the size limits of the window. diff --git a/src/config/Config.zig b/src/config/Config.zig index 7d8d648df..153ede2e5 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -1108,7 +1108,7 @@ keybind: Keybinds = .{}, @"window-height": u32 = 0, @"window-width": u32 = 0, -/// The initial window position. This position is in pixels and is relative +/// 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. /// @@ -1117,10 +1117,22 @@ keybind: Keybinds = .{}, /// to be a certain position to fit within the grid. There is nothing Ghostty /// will do about this, but it will make an effort. /// -/// Important note: Setting this value will only work on macOS and glfw builds -/// on Linux. GTK 4.0 does not support setting the window position. -@"window-initial-position-x": ?i16 = null, -@"window-initial-position-y": ?i16 = null, +/// 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