Skip to content

Conversation

@brtmax
Copy link

@brtmax brtmax commented Oct 23, 2025

Related to #8387

This implements cycling through windows (next/previous) by using the list gtk provides, as mentioned in the discussion in #8387 . I tried to follow the same pattern as 'goto_tab' and 'goto_split'. It presents the next target window and then transfers the focus to its active surface.

Sorry, this took some time. Also, this is a GTK implementation only, I currently don't have access to Mac hardware. Hope this is enough to start.

AI Disclosure:
I used deepwiki.com to familiarize myself with the repository and look for patterns to reuse.

@brtmax brtmax requested review from a team as code owners October 23, 2025 21:49
@brtmax brtmax closed this Oct 23, 2025
@brtmax brtmax reopened this Oct 23, 2025
Copy link
Member

@jcollie jcollie left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall very good, but a few things need to be cleaned up. Thank you for the AI disclosure.

const target_node = switch (direction) {
.next => node.f_next orelse glist,
.previous => node.f_prev orelse last: {
var current = glist;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not make use of the first and last list functions?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for all the feedback! I believe the Zig wrapper for this does not expose those two functions, so in order to use them I would have to import the C headers and then cast the wrapper type to the C pointer type. This is my first real Zig project, maybe I got it wrong. I thought the current way would maybe be cleaner. How should I proceed?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They very much are present: https://ianjohnson.dev/zig-gobject/#glib2.List.last
Although I will be honest and say that the function signatures suck (in the C API, an empty list is represented as a null so all list-related APIs should be nullable), so it might be better to just do it in pure Zig instead.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the hint, I think I understand it better now. I've kept the pure Zig version but added Null handling similar to the quit function by making the result of the findCustom optional and falling back to the first window when no active window is found, is this better or do you want me to change it?

@@ -884,6 +847,11 @@ pub const Action = union(enum) {
right,
};

pub const WindowDirection = enum {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason this is named differently here? Why not keep it GotoWindow as well?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My bad, overlooked this from an earlier version. Changed it to GotoWindow now

.description = "Focus the previous window, if any.",
},
.{
.action = .{ .goto_window = .previous },
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using the wrong action here.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, should be fixed now

move_tab,
goto_tab,
goto_split,
goto_window,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to be added to the corresponding enum in ghostty.h as well.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added

src/Surface.zig Outdated
.{ .surface = self },
.goto_window,
switch (direction) {
inline else => |tag| @field(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The inline else seems like overkill here since there are only two values and it's unlikely to ever change.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replaced this with directly handling next/previous cases

@bo2themax
Copy link
Member

@brtmax there are some conflicts now

@brtmax brtmax requested a review from a team as a code owner October 24, 2025 15:03
@brtmax
Copy link
Author

brtmax commented Oct 24, 2025

Sorry, I forgot the upstream changes

@bo2themax
Copy link
Member

Sorry, I forgot the upstream changes

Nothing to be sorry about, it happens all the time. Thanks for your contribution, looks good to me, but let’s wait for the gtk or terminal team to approve this.

const gtk_window: *gtk.Window = @ptrCast(@alignCast(target_node.f_data orelse return false));
gtk.Window.present(gtk_window);

const ghostty_window: *Window = @ptrCast(@alignCast(gtk_window));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't do this for GTK wrapper types. Use a type-safe alternative like gobject.ext.as when upcasting to a parent type (and it's comptime known that this is safe), and gobject.ext.cast when downcasting to a child type.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to apply it, is this what you had in mind? Also thanks for the feedback as well

_ = value.init(gobject.ext.typeFor(?*Surface));
ghostty_window.as(gobject.Object).getProperty("active-surface", &value);

const surface: ?*Surface = @ptrCast(@alignCast(value.getObject()));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto here

Comment on lines 2017 to 2020
var value = std.mem.zeroes(gobject.Value);
defer value.unset();
_ = value.init(gobject.ext.typeFor(?*Surface));
ghostty_window.as(gobject.Object).getProperty("active-surface", &value);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think using gobject.Object.get, even though it's only supposed to be used in C, might make things a bit cleaner here.

var surface: ?*gobject.Object = null;
ghostty_window.as(gobject.Object).get("active-surface", &surface, @as(?*anyopaque, null));
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added

src/Surface.zig Outdated
Comment on lines 4982 to 4983
.next => apprt.action.GotoWindow.next,
.previous => apprt.action.GotoWindow.previous,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Decl literals should just work here, no?

Suggested change
.next => apprt.action.GotoWindow.next,
.previous => apprt.action.GotoWindow.previous,
.next => .next,
.previous => .previous,
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, yes it works! I noticed that I got the ordering inconsistent in this part, so I committed it again with correct ordering.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

4 participants