@@ -501,6 +501,9 @@ extension Ghostty {
501501 case GHOSTTY_ACTION_GOTO_SPLIT:
502502 return gotoSplit ( app, target: target, direction: action. action. goto_split)
503503
504+ case GHOSTTY_ACTION_GOTO_WINDOW:
505+ return gotoWindow ( app, target: target, direction: action. action. goto_window)
506+
504507 case GHOSTTY_ACTION_RESIZE_SPLIT:
505508 resizeSplit ( app, target: target, resize: action. action. resize_split)
506509
@@ -1149,6 +1152,64 @@ extension Ghostty {
11491152 }
11501153 }
11511154
1155+ private static func gotoWindow(
1156+ _ app: ghostty_app_t ,
1157+ target: ghostty_target_s ,
1158+ direction: ghostty_action_goto_window_e
1159+ ) -> Bool {
1160+ // Collect candidate windows: visible terminal windows that are either
1161+ // standalone or the currently selected tab in their tab group. This
1162+ // treats each native tab group as a single "window" for navigation
1163+ // purposes, since goto_tab handles per-tab navigation.
1164+ let candidates : [ NSWindow ] = NSApplication . shared. windows. filter { window in
1165+ guard window. windowController is BaseTerminalController else { return false }
1166+ guard window. isVisible, !window. isMiniaturized else { return false }
1167+ // For native tabs, only include the selected tab in each group
1168+ if let group = window. tabGroup, group. selectedWindow !== window {
1169+ return false
1170+ }
1171+ return true
1172+ }
1173+
1174+ // Need at least two windows to navigate between
1175+ guard candidates. count > 1 else { return false }
1176+
1177+ // Find starting index from the current key/main window
1178+ let startIndex = candidates. firstIndex ( where: { $0. isKeyWindow } )
1179+ ?? candidates. firstIndex ( where: { $0. isMainWindow } )
1180+ ?? 0
1181+
1182+ let step : Int
1183+ switch direction {
1184+ case GHOSTTY_GOTO_WINDOW_NEXT:
1185+ step = 1
1186+ case GHOSTTY_GOTO_WINDOW_PREVIOUS:
1187+ step = - 1
1188+ default :
1189+ return false
1190+ }
1191+
1192+ // Iterate with wrap-around until we find a valid window or return to start
1193+ let count = candidates. count
1194+ var index = ( startIndex + step + count) % count
1195+
1196+ while index != startIndex {
1197+ let candidate = candidates [ index]
1198+ if candidate. isVisible, !candidate. isMiniaturized {
1199+ candidate. makeKeyAndOrderFront ( nil )
1200+ // Also focus the terminal surface within the window
1201+ if let controller = candidate. windowController as? BaseTerminalController ,
1202+ let surface = controller. focusedSurface {
1203+ Ghostty . moveFocus ( to: surface)
1204+ }
1205+ return true
1206+ }
1207+ index = ( index + step + count) % count
1208+ }
1209+
1210+ return false
1211+ }
1212+
11521213 private static func resizeSplit(
11531214 _ app: ghostty_app_t ,
11541215 target: ghostty_target_s ,
0 commit comments