From 5ced72498e122bcdf0b5c390f72a389ddbf2f1f5 Mon Sep 17 00:00:00 2001 From: Adam Wolf Date: Sun, 29 Dec 2024 11:23:54 -0600 Subject: [PATCH] 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`