mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 16:56:09 +03:00
Merge pull request #2305 from ghostty-org/macos-dispatch
libghostty: unified action dispatch
This commit is contained in:
@ -30,7 +30,9 @@ typedef void* ghostty_config_t;
|
|||||||
typedef void* ghostty_surface_t;
|
typedef void* ghostty_surface_t;
|
||||||
typedef void* ghostty_inspector_t;
|
typedef void* ghostty_inspector_t;
|
||||||
|
|
||||||
// Enums are up top so we can reference them later.
|
// All the types below are fully defined and must be kept in sync with
|
||||||
|
// their Zig counterparts. Any changes to these types MUST have an associated
|
||||||
|
// Zig change.
|
||||||
typedef enum {
|
typedef enum {
|
||||||
GHOSTTY_PLATFORM_INVALID,
|
GHOSTTY_PLATFORM_INVALID,
|
||||||
GHOSTTY_PLATFORM_MACOS,
|
GHOSTTY_PLATFORM_MACOS,
|
||||||
@ -48,33 +50,6 @@ typedef enum {
|
|||||||
GHOSTTY_CLIPBOARD_REQUEST_OSC_52_WRITE,
|
GHOSTTY_CLIPBOARD_REQUEST_OSC_52_WRITE,
|
||||||
} ghostty_clipboard_request_e;
|
} ghostty_clipboard_request_e;
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
GHOSTTY_SPLIT_RIGHT,
|
|
||||||
GHOSTTY_SPLIT_DOWN
|
|
||||||
} ghostty_split_direction_e;
|
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
GHOSTTY_SPLIT_FOCUS_PREVIOUS,
|
|
||||||
GHOSTTY_SPLIT_FOCUS_NEXT,
|
|
||||||
GHOSTTY_SPLIT_FOCUS_TOP,
|
|
||||||
GHOSTTY_SPLIT_FOCUS_LEFT,
|
|
||||||
GHOSTTY_SPLIT_FOCUS_BOTTOM,
|
|
||||||
GHOSTTY_SPLIT_FOCUS_RIGHT,
|
|
||||||
} ghostty_split_focus_direction_e;
|
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
GHOSTTY_SPLIT_RESIZE_UP,
|
|
||||||
GHOSTTY_SPLIT_RESIZE_DOWN,
|
|
||||||
GHOSTTY_SPLIT_RESIZE_LEFT,
|
|
||||||
GHOSTTY_SPLIT_RESIZE_RIGHT,
|
|
||||||
} ghostty_split_resize_direction_e;
|
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
GHOSTTY_INSPECTOR_TOGGLE,
|
|
||||||
GHOSTTY_INSPECTOR_SHOW,
|
|
||||||
GHOSTTY_INSPECTOR_HIDE,
|
|
||||||
} ghostty_inspector_mode_e;
|
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
GHOSTTY_MOUSE_RELEASE,
|
GHOSTTY_MOUSE_RELEASE,
|
||||||
GHOSTTY_MOUSE_PRESS,
|
GHOSTTY_MOUSE_PRESS,
|
||||||
@ -97,55 +72,6 @@ typedef enum {
|
|||||||
GHOSTTY_MOUSE_MOMENTUM_MAY_BEGIN,
|
GHOSTTY_MOUSE_MOMENTUM_MAY_BEGIN,
|
||||||
} ghostty_input_mouse_momentum_e;
|
} ghostty_input_mouse_momentum_e;
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
GHOSTTY_MOUSE_SHAPE_DEFAULT,
|
|
||||||
GHOSTTY_MOUSE_SHAPE_CONTEXT_MENU,
|
|
||||||
GHOSTTY_MOUSE_SHAPE_HELP,
|
|
||||||
GHOSTTY_MOUSE_SHAPE_POINTER,
|
|
||||||
GHOSTTY_MOUSE_SHAPE_PROGRESS,
|
|
||||||
GHOSTTY_MOUSE_SHAPE_WAIT,
|
|
||||||
GHOSTTY_MOUSE_SHAPE_CELL,
|
|
||||||
GHOSTTY_MOUSE_SHAPE_CROSSHAIR,
|
|
||||||
GHOSTTY_MOUSE_SHAPE_TEXT,
|
|
||||||
GHOSTTY_MOUSE_SHAPE_VERTICAL_TEXT,
|
|
||||||
GHOSTTY_MOUSE_SHAPE_ALIAS,
|
|
||||||
GHOSTTY_MOUSE_SHAPE_COPY,
|
|
||||||
GHOSTTY_MOUSE_SHAPE_MOVE,
|
|
||||||
GHOSTTY_MOUSE_SHAPE_NO_DROP,
|
|
||||||
GHOSTTY_MOUSE_SHAPE_NOT_ALLOWED,
|
|
||||||
GHOSTTY_MOUSE_SHAPE_GRAB,
|
|
||||||
GHOSTTY_MOUSE_SHAPE_GRABBING,
|
|
||||||
GHOSTTY_MOUSE_SHAPE_ALL_SCROLL,
|
|
||||||
GHOSTTY_MOUSE_SHAPE_COL_RESIZE,
|
|
||||||
GHOSTTY_MOUSE_SHAPE_ROW_RESIZE,
|
|
||||||
GHOSTTY_MOUSE_SHAPE_N_RESIZE,
|
|
||||||
GHOSTTY_MOUSE_SHAPE_E_RESIZE,
|
|
||||||
GHOSTTY_MOUSE_SHAPE_S_RESIZE,
|
|
||||||
GHOSTTY_MOUSE_SHAPE_W_RESIZE,
|
|
||||||
GHOSTTY_MOUSE_SHAPE_NE_RESIZE,
|
|
||||||
GHOSTTY_MOUSE_SHAPE_NW_RESIZE,
|
|
||||||
GHOSTTY_MOUSE_SHAPE_SE_RESIZE,
|
|
||||||
GHOSTTY_MOUSE_SHAPE_SW_RESIZE,
|
|
||||||
GHOSTTY_MOUSE_SHAPE_EW_RESIZE,
|
|
||||||
GHOSTTY_MOUSE_SHAPE_NS_RESIZE,
|
|
||||||
GHOSTTY_MOUSE_SHAPE_NESW_RESIZE,
|
|
||||||
GHOSTTY_MOUSE_SHAPE_NWSE_RESIZE,
|
|
||||||
GHOSTTY_MOUSE_SHAPE_ZOOM_IN,
|
|
||||||
GHOSTTY_MOUSE_SHAPE_ZOOM_OUT,
|
|
||||||
} ghostty_mouse_shape_e;
|
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
GHOSTTY_NON_NATIVE_FULLSCREEN_FALSE,
|
|
||||||
GHOSTTY_NON_NATIVE_FULLSCREEN_TRUE,
|
|
||||||
GHOSTTY_NON_NATIVE_FULLSCREEN_VISIBLE_MENU,
|
|
||||||
} ghostty_non_native_fullscreen_e;
|
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
GHOSTTY_TAB_PREVIOUS = -1,
|
|
||||||
GHOSTTY_TAB_NEXT = -2,
|
|
||||||
GHOSTTY_TAB_LAST = -3,
|
|
||||||
} ghostty_tab_e;
|
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
GHOSTTY_COLOR_SCHEME_LIGHT = 0,
|
GHOSTTY_COLOR_SCHEME_LIGHT = 0,
|
||||||
GHOSTTY_COLOR_SCHEME_DARK = 1,
|
GHOSTTY_COLOR_SCHEME_DARK = 1,
|
||||||
@ -357,14 +283,6 @@ typedef enum {
|
|||||||
GHOSTTY_BUILD_MODE_RELEASE_SMALL,
|
GHOSTTY_BUILD_MODE_RELEASE_SMALL,
|
||||||
} ghostty_build_mode_e;
|
} ghostty_build_mode_e;
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
GHOSTTY_RENDERER_HEALTH_OK,
|
|
||||||
GHOSTTY_RENDERER_HEALTH_UNHEALTHY,
|
|
||||||
} ghostty_renderer_health_e;
|
|
||||||
|
|
||||||
// Fully defined types. This MUST be kept in sync with equivalent Zig
|
|
||||||
// structs. To find the Zig struct, grep for this type name. The documentation
|
|
||||||
// for all of these types is available in the Zig source.
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
ghostty_build_mode_e build_mode;
|
ghostty_build_mode_e build_mode;
|
||||||
const char* version;
|
const char* version;
|
||||||
@ -414,13 +332,229 @@ typedef struct {
|
|||||||
uint32_t cell_height_px;
|
uint32_t cell_height_px;
|
||||||
} ghostty_surface_size_s;
|
} ghostty_surface_size_s;
|
||||||
|
|
||||||
|
// apprt.Target.Key
|
||||||
|
typedef enum {
|
||||||
|
GHOSTTY_TARGET_APP,
|
||||||
|
GHOSTTY_TARGET_SURFACE,
|
||||||
|
} ghostty_target_tag_e;
|
||||||
|
|
||||||
|
typedef union {
|
||||||
|
ghostty_surface_t surface;
|
||||||
|
} ghostty_target_u;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
ghostty_target_tag_e tag;
|
||||||
|
ghostty_target_u target;
|
||||||
|
} ghostty_target_s;
|
||||||
|
|
||||||
|
// apprt.action.SplitDirection
|
||||||
|
typedef enum {
|
||||||
|
GHOSTTY_SPLIT_DIRECTION_RIGHT,
|
||||||
|
GHOSTTY_SPLIT_DIRECTION_DOWN,
|
||||||
|
} ghostty_action_split_direction_e;
|
||||||
|
|
||||||
|
// apprt.action.GotoSplit
|
||||||
|
typedef enum {
|
||||||
|
GHOSTTY_GOTO_SPLIT_PREVIOUS,
|
||||||
|
GHOSTTY_GOTO_SPLIT_NEXT,
|
||||||
|
GHOSTTY_GOTO_SPLIT_TOP,
|
||||||
|
GHOSTTY_GOTO_SPLIT_LEFT,
|
||||||
|
GHOSTTY_GOTO_SPLIT_BOTTOM,
|
||||||
|
GHOSTTY_GOTO_SPLIT_RIGHT,
|
||||||
|
} ghostty_action_goto_split_e;
|
||||||
|
|
||||||
|
// apprt.action.ResizeSplit.Direction
|
||||||
|
typedef enum {
|
||||||
|
GHOSTTY_RESIZE_SPLIT_UP,
|
||||||
|
GHOSTTY_RESIZE_SPLIT_DOWN,
|
||||||
|
GHOSTTY_RESIZE_SPLIT_LEFT,
|
||||||
|
GHOSTTY_RESIZE_SPLIT_RIGHT,
|
||||||
|
} ghostty_action_resize_split_direction_e;
|
||||||
|
|
||||||
|
// apprt.action.ResizeSplit
|
||||||
|
typedef struct {
|
||||||
|
uint16_t amount;
|
||||||
|
ghostty_action_resize_split_direction_e direction;
|
||||||
|
} ghostty_action_resize_split_s;
|
||||||
|
|
||||||
|
// apprt.action.GotoTab
|
||||||
|
typedef enum {
|
||||||
|
GHOSTTY_GOTO_TAB_PREVIOUS,
|
||||||
|
GHOSTTY_GOTO_TAB_NEXT,
|
||||||
|
GHOSTTY_GOTO_TAB_LAST,
|
||||||
|
} ghostty_action_goto_tab_e;
|
||||||
|
|
||||||
|
// apprt.action.Fullscreen
|
||||||
|
typedef enum {
|
||||||
|
GHOSTTY_FULLSCREEN_NATIVE,
|
||||||
|
GHOSTTY_FULLSCREEN_NON_NATIVE,
|
||||||
|
GHOSTTY_FULLSCREEN_NON_NATIVE_VISIBLE_MENU,
|
||||||
|
} ghostty_action_fullscreen_e;
|
||||||
|
|
||||||
|
// apprt.action.SecureInput
|
||||||
|
typedef enum {
|
||||||
|
GHOSTTY_SECURE_INPUT_ON,
|
||||||
|
GHOSTTY_SECURE_INPUT_OFF,
|
||||||
|
GHOSTTY_SECURE_INPUT_TOGGLE,
|
||||||
|
} ghostty_action_secure_input_e;
|
||||||
|
|
||||||
|
// apprt.action.Inspector
|
||||||
|
typedef enum {
|
||||||
|
GHOSTTY_INSPECTOR_TOGGLE,
|
||||||
|
GHOSTTY_INSPECTOR_SHOW,
|
||||||
|
GHOSTTY_INSPECTOR_HIDE,
|
||||||
|
} ghostty_action_inspector_e;
|
||||||
|
|
||||||
|
// apprt.action.QuitTimer
|
||||||
|
typedef enum {
|
||||||
|
GHOSTTY_QUIT_TIMER_START,
|
||||||
|
GHOSTTY_QUIT_TIMER_STOP,
|
||||||
|
} ghostty_action_quit_timer_e;
|
||||||
|
|
||||||
|
// apprt.action.DesktopNotification.C
|
||||||
|
typedef struct {
|
||||||
|
const char* title;
|
||||||
|
const char* body;
|
||||||
|
} ghostty_action_desktop_notification_s;
|
||||||
|
|
||||||
|
// apprt.action.SetTitle.C
|
||||||
|
typedef struct {
|
||||||
|
const char* title;
|
||||||
|
} ghostty_action_set_title_s;
|
||||||
|
|
||||||
|
// terminal.MouseShape
|
||||||
|
typedef enum {
|
||||||
|
GHOSTTY_MOUSE_SHAPE_DEFAULT,
|
||||||
|
GHOSTTY_MOUSE_SHAPE_CONTEXT_MENU,
|
||||||
|
GHOSTTY_MOUSE_SHAPE_HELP,
|
||||||
|
GHOSTTY_MOUSE_SHAPE_POINTER,
|
||||||
|
GHOSTTY_MOUSE_SHAPE_PROGRESS,
|
||||||
|
GHOSTTY_MOUSE_SHAPE_WAIT,
|
||||||
|
GHOSTTY_MOUSE_SHAPE_CELL,
|
||||||
|
GHOSTTY_MOUSE_SHAPE_CROSSHAIR,
|
||||||
|
GHOSTTY_MOUSE_SHAPE_TEXT,
|
||||||
|
GHOSTTY_MOUSE_SHAPE_VERTICAL_TEXT,
|
||||||
|
GHOSTTY_MOUSE_SHAPE_ALIAS,
|
||||||
|
GHOSTTY_MOUSE_SHAPE_COPY,
|
||||||
|
GHOSTTY_MOUSE_SHAPE_MOVE,
|
||||||
|
GHOSTTY_MOUSE_SHAPE_NO_DROP,
|
||||||
|
GHOSTTY_MOUSE_SHAPE_NOT_ALLOWED,
|
||||||
|
GHOSTTY_MOUSE_SHAPE_GRAB,
|
||||||
|
GHOSTTY_MOUSE_SHAPE_GRABBING,
|
||||||
|
GHOSTTY_MOUSE_SHAPE_ALL_SCROLL,
|
||||||
|
GHOSTTY_MOUSE_SHAPE_COL_RESIZE,
|
||||||
|
GHOSTTY_MOUSE_SHAPE_ROW_RESIZE,
|
||||||
|
GHOSTTY_MOUSE_SHAPE_N_RESIZE,
|
||||||
|
GHOSTTY_MOUSE_SHAPE_E_RESIZE,
|
||||||
|
GHOSTTY_MOUSE_SHAPE_S_RESIZE,
|
||||||
|
GHOSTTY_MOUSE_SHAPE_W_RESIZE,
|
||||||
|
GHOSTTY_MOUSE_SHAPE_NE_RESIZE,
|
||||||
|
GHOSTTY_MOUSE_SHAPE_NW_RESIZE,
|
||||||
|
GHOSTTY_MOUSE_SHAPE_SE_RESIZE,
|
||||||
|
GHOSTTY_MOUSE_SHAPE_SW_RESIZE,
|
||||||
|
GHOSTTY_MOUSE_SHAPE_EW_RESIZE,
|
||||||
|
GHOSTTY_MOUSE_SHAPE_NS_RESIZE,
|
||||||
|
GHOSTTY_MOUSE_SHAPE_NESW_RESIZE,
|
||||||
|
GHOSTTY_MOUSE_SHAPE_NWSE_RESIZE,
|
||||||
|
GHOSTTY_MOUSE_SHAPE_ZOOM_IN,
|
||||||
|
GHOSTTY_MOUSE_SHAPE_ZOOM_OUT,
|
||||||
|
} ghostty_action_mouse_shape_e;
|
||||||
|
|
||||||
|
// apprt.action.MouseVisibility
|
||||||
|
typedef enum {
|
||||||
|
GHOSTTY_MOUSE_VISIBLE,
|
||||||
|
GHOSTTY_MOUSE_HIDDEN,
|
||||||
|
} ghostty_action_mouse_visibility_e;
|
||||||
|
|
||||||
|
// apprt.action.MouseOverLink
|
||||||
|
typedef struct {
|
||||||
|
const char* url;
|
||||||
|
size_t len;
|
||||||
|
} ghostty_action_mouse_over_link_s;
|
||||||
|
|
||||||
|
// apprt.action.SizeLimit
|
||||||
|
typedef struct {
|
||||||
|
uint32_t min_width;
|
||||||
|
uint32_t min_height;
|
||||||
|
uint32_t max_width;
|
||||||
|
uint32_t max_height;
|
||||||
|
} ghostty_action_size_limit_s;
|
||||||
|
|
||||||
|
// apprt.action.InitialSize
|
||||||
|
typedef struct {
|
||||||
|
uint32_t width;
|
||||||
|
uint32_t height;
|
||||||
|
} ghostty_action_initial_size_s;
|
||||||
|
|
||||||
|
// apprt.action.CellSize
|
||||||
|
typedef struct {
|
||||||
|
uint32_t width;
|
||||||
|
uint32_t height;
|
||||||
|
} ghostty_action_cell_size_s;
|
||||||
|
|
||||||
|
// renderer.Health
|
||||||
|
typedef enum {
|
||||||
|
GHOSTTY_RENDERER_HEALTH_OK,
|
||||||
|
GHOSTTY_RENDERER_HEALTH_UNHEALTHY,
|
||||||
|
} ghostty_action_renderer_health_e;
|
||||||
|
|
||||||
|
// apprt.Action.Key
|
||||||
|
typedef enum {
|
||||||
|
GHOSTTY_ACTION_NEW_WINDOW,
|
||||||
|
GHOSTTY_ACTION_NEW_TAB,
|
||||||
|
GHOSTTY_ACTION_NEW_SPLIT,
|
||||||
|
GHOSTTY_ACTION_CLOSE_ALL_WINDOWS,
|
||||||
|
GHOSTTY_ACTION_TOGGLE_FULLSCREEN,
|
||||||
|
GHOSTTY_ACTION_TOGGLE_WINDOW_DECORATIONS,
|
||||||
|
GHOSTTY_ACTION_GOTO_TAB,
|
||||||
|
GHOSTTY_ACTION_GOTO_SPLIT,
|
||||||
|
GHOSTTY_ACTION_RESIZE_SPLIT,
|
||||||
|
GHOSTTY_ACTION_EQUALIZE_SPLITS,
|
||||||
|
GHOSTTY_ACTION_TOGGLE_SPLIT_ZOOM,
|
||||||
|
GHOSTTY_ACTION_PRESENT_TERMINAL,
|
||||||
|
GHOSTTY_ACTION_SIZE_LIMIT,
|
||||||
|
GHOSTTY_ACTION_INITIAL_SIZE,
|
||||||
|
GHOSTTY_ACTION_CELL_SIZE,
|
||||||
|
GHOSTTY_ACTION_INSPECTOR,
|
||||||
|
GHOSTTY_ACTION_RENDER_INSPECTOR,
|
||||||
|
GHOSTTY_ACTION_DESKTOP_NOTIFICATION,
|
||||||
|
GHOSTTY_ACTION_SET_TITLE,
|
||||||
|
GHOSTTY_ACTION_MOUSE_SHAPE,
|
||||||
|
GHOSTTY_ACTION_MOUSE_VISIBILITY,
|
||||||
|
GHOSTTY_ACTION_MOUSE_OVER_LINK,
|
||||||
|
GHOSTTY_ACTION_RENDERER_HEALTH,
|
||||||
|
GHOSTTY_ACTION_OPEN_CONFIG,
|
||||||
|
GHOSTTY_ACTION_QUIT_TIMER,
|
||||||
|
GHOSTTY_ACTION_SECURE_INPUT,
|
||||||
|
} ghostty_action_tag_e;
|
||||||
|
|
||||||
|
typedef union {
|
||||||
|
ghostty_action_split_direction_e new_split;
|
||||||
|
ghostty_action_fullscreen_e toggle_fullscreen;
|
||||||
|
ghostty_action_goto_tab_e goto_tab;
|
||||||
|
ghostty_action_goto_split_e goto_split;
|
||||||
|
ghostty_action_resize_split_s resize_split;
|
||||||
|
ghostty_action_size_limit_s size_limit;
|
||||||
|
ghostty_action_initial_size_s initial_size;
|
||||||
|
ghostty_action_cell_size_s cell_size;
|
||||||
|
ghostty_action_inspector_e inspector;
|
||||||
|
ghostty_action_desktop_notification_s desktop_notification;
|
||||||
|
ghostty_action_set_title_s set_title;
|
||||||
|
ghostty_action_mouse_shape_e mouse_shape;
|
||||||
|
ghostty_action_mouse_visibility_e mouse_visibility;
|
||||||
|
ghostty_action_mouse_over_link_s mouse_over_link;
|
||||||
|
ghostty_action_renderer_health_e renderer_health;
|
||||||
|
ghostty_action_quit_timer_e quit_timer;
|
||||||
|
ghostty_action_secure_input_e secure_input;
|
||||||
|
} ghostty_action_u;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
ghostty_action_tag_e tag;
|
||||||
|
ghostty_action_u action;
|
||||||
|
} ghostty_action_s;
|
||||||
|
|
||||||
typedef void (*ghostty_runtime_wakeup_cb)(void*);
|
typedef void (*ghostty_runtime_wakeup_cb)(void*);
|
||||||
typedef const ghostty_config_t (*ghostty_runtime_reload_config_cb)(void*);
|
typedef const ghostty_config_t (*ghostty_runtime_reload_config_cb)(void*);
|
||||||
typedef void (*ghostty_runtime_open_config_cb)(void*);
|
|
||||||
typedef void (*ghostty_runtime_set_title_cb)(void*, const char*);
|
|
||||||
typedef void (*ghostty_runtime_set_mouse_shape_cb)(void*,
|
|
||||||
ghostty_mouse_shape_e);
|
|
||||||
typedef void (*ghostty_runtime_set_mouse_visibility_cb)(void*, bool);
|
|
||||||
typedef void (*ghostty_runtime_read_clipboard_cb)(void*,
|
typedef void (*ghostty_runtime_read_clipboard_cb)(void*,
|
||||||
ghostty_clipboard_e,
|
ghostty_clipboard_e,
|
||||||
void*);
|
void*);
|
||||||
@ -433,71 +567,21 @@ typedef void (*ghostty_runtime_write_clipboard_cb)(void*,
|
|||||||
const char*,
|
const char*,
|
||||||
ghostty_clipboard_e,
|
ghostty_clipboard_e,
|
||||||
bool);
|
bool);
|
||||||
typedef void (*ghostty_runtime_new_split_cb)(void*,
|
|
||||||
ghostty_split_direction_e,
|
|
||||||
ghostty_surface_config_s);
|
|
||||||
typedef void (*ghostty_runtime_new_tab_cb)(void*, ghostty_surface_config_s);
|
|
||||||
typedef void (*ghostty_runtime_new_window_cb)(void*, ghostty_surface_config_s);
|
|
||||||
typedef void (*ghostty_runtime_control_inspector_cb)(void*,
|
|
||||||
ghostty_inspector_mode_e);
|
|
||||||
typedef void (*ghostty_runtime_close_surface_cb)(void*, bool);
|
typedef void (*ghostty_runtime_close_surface_cb)(void*, bool);
|
||||||
typedef void (*ghostty_runtime_focus_split_cb)(void*,
|
typedef void (*ghostty_runtime_action_cb)(ghostty_app_t,
|
||||||
ghostty_split_focus_direction_e);
|
ghostty_target_s,
|
||||||
typedef void (*ghostty_runtime_resize_split_cb)(
|
ghostty_action_s);
|
||||||
void*,
|
|
||||||
ghostty_split_resize_direction_e,
|
|
||||||
uint16_t);
|
|
||||||
typedef void (*ghostty_runtime_equalize_splits_cb)(void*);
|
|
||||||
typedef void (*ghostty_runtime_toggle_split_zoom_cb)(void*);
|
|
||||||
typedef void (*ghostty_runtime_goto_tab_cb)(void*, int32_t);
|
|
||||||
typedef void (*ghostty_runtime_toggle_fullscreen_cb)(
|
|
||||||
void*,
|
|
||||||
ghostty_non_native_fullscreen_e);
|
|
||||||
typedef void (*ghostty_runtime_set_initial_window_size_cb)(void*,
|
|
||||||
uint32_t,
|
|
||||||
uint32_t);
|
|
||||||
typedef void (*ghostty_runtime_render_inspector_cb)(void*);
|
|
||||||
typedef void (*ghostty_runtime_set_cell_size_cb)(void*, uint32_t, uint32_t);
|
|
||||||
typedef void (*ghostty_runtime_show_desktop_notification_cb)(void*,
|
|
||||||
const char*,
|
|
||||||
const char*);
|
|
||||||
typedef void (
|
|
||||||
*ghostty_runtime_update_renderer_health)(void*, ghostty_renderer_health_e);
|
|
||||||
typedef void (*ghostty_runtime_mouse_over_link_cb)(void*, const char*, size_t);
|
|
||||||
typedef void (*ghostty_runtime_set_password_input_cb)(void*, bool);
|
|
||||||
typedef void (*ghostty_runtime_toggle_secure_input_cb)();
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
void* userdata;
|
void* userdata;
|
||||||
bool supports_selection_clipboard;
|
bool supports_selection_clipboard;
|
||||||
ghostty_runtime_wakeup_cb wakeup_cb;
|
ghostty_runtime_wakeup_cb wakeup_cb;
|
||||||
|
ghostty_runtime_action_cb action_cb;
|
||||||
ghostty_runtime_reload_config_cb reload_config_cb;
|
ghostty_runtime_reload_config_cb reload_config_cb;
|
||||||
ghostty_runtime_open_config_cb open_config_cb;
|
|
||||||
ghostty_runtime_set_title_cb set_title_cb;
|
|
||||||
ghostty_runtime_set_mouse_shape_cb set_mouse_shape_cb;
|
|
||||||
ghostty_runtime_set_mouse_visibility_cb set_mouse_visibility_cb;
|
|
||||||
ghostty_runtime_read_clipboard_cb read_clipboard_cb;
|
ghostty_runtime_read_clipboard_cb read_clipboard_cb;
|
||||||
ghostty_runtime_confirm_read_clipboard_cb confirm_read_clipboard_cb;
|
ghostty_runtime_confirm_read_clipboard_cb confirm_read_clipboard_cb;
|
||||||
ghostty_runtime_write_clipboard_cb write_clipboard_cb;
|
ghostty_runtime_write_clipboard_cb write_clipboard_cb;
|
||||||
ghostty_runtime_new_split_cb new_split_cb;
|
|
||||||
ghostty_runtime_new_tab_cb new_tab_cb;
|
|
||||||
ghostty_runtime_new_window_cb new_window_cb;
|
|
||||||
ghostty_runtime_control_inspector_cb control_inspector_cb;
|
|
||||||
ghostty_runtime_close_surface_cb close_surface_cb;
|
ghostty_runtime_close_surface_cb close_surface_cb;
|
||||||
ghostty_runtime_focus_split_cb focus_split_cb;
|
|
||||||
ghostty_runtime_resize_split_cb resize_split_cb;
|
|
||||||
ghostty_runtime_equalize_splits_cb equalize_splits_cb;
|
|
||||||
ghostty_runtime_toggle_split_zoom_cb toggle_split_zoom_cb;
|
|
||||||
ghostty_runtime_goto_tab_cb goto_tab_cb;
|
|
||||||
ghostty_runtime_toggle_fullscreen_cb toggle_fullscreen_cb;
|
|
||||||
ghostty_runtime_set_initial_window_size_cb set_initial_window_size_cb;
|
|
||||||
ghostty_runtime_render_inspector_cb render_inspector_cb;
|
|
||||||
ghostty_runtime_set_cell_size_cb set_cell_size_cb;
|
|
||||||
ghostty_runtime_show_desktop_notification_cb show_desktop_notification_cb;
|
|
||||||
ghostty_runtime_update_renderer_health update_renderer_health_cb;
|
|
||||||
ghostty_runtime_mouse_over_link_cb mouse_over_link_cb;
|
|
||||||
ghostty_runtime_set_password_input_cb set_password_input_cb;
|
|
||||||
ghostty_runtime_toggle_secure_input_cb toggle_secure_input_cb;
|
|
||||||
} ghostty_runtime_config_s;
|
} ghostty_runtime_config_s;
|
||||||
|
|
||||||
//-------------------------------------------------------------------
|
//-------------------------------------------------------------------
|
||||||
@ -538,7 +622,9 @@ ghostty_surface_config_s ghostty_surface_config_new();
|
|||||||
|
|
||||||
ghostty_surface_t ghostty_surface_new(ghostty_app_t, ghostty_surface_config_s*);
|
ghostty_surface_t ghostty_surface_new(ghostty_app_t, ghostty_surface_config_s*);
|
||||||
void ghostty_surface_free(ghostty_surface_t);
|
void ghostty_surface_free(ghostty_surface_t);
|
||||||
|
void* ghostty_surface_userdata(ghostty_surface_t);
|
||||||
ghostty_app_t ghostty_surface_app(ghostty_surface_t);
|
ghostty_app_t ghostty_surface_app(ghostty_surface_t);
|
||||||
|
ghostty_surface_config_s ghostty_surface_inherited_config(ghostty_surface_t);
|
||||||
bool ghostty_surface_needs_confirm_quit(ghostty_surface_t);
|
bool ghostty_surface_needs_confirm_quit(ghostty_surface_t);
|
||||||
void ghostty_surface_refresh(ghostty_surface_t);
|
void ghostty_surface_refresh(ghostty_surface_t);
|
||||||
void ghostty_surface_draw(ghostty_surface_t);
|
void ghostty_surface_draw(ghostty_surface_t);
|
||||||
@ -569,11 +655,11 @@ void ghostty_surface_mouse_scroll(ghostty_surface_t,
|
|||||||
void ghostty_surface_mouse_pressure(ghostty_surface_t, uint32_t, double);
|
void ghostty_surface_mouse_pressure(ghostty_surface_t, uint32_t, double);
|
||||||
void ghostty_surface_ime_point(ghostty_surface_t, double*, double*);
|
void ghostty_surface_ime_point(ghostty_surface_t, double*, double*);
|
||||||
void ghostty_surface_request_close(ghostty_surface_t);
|
void ghostty_surface_request_close(ghostty_surface_t);
|
||||||
void ghostty_surface_split(ghostty_surface_t, ghostty_split_direction_e);
|
void ghostty_surface_split(ghostty_surface_t, ghostty_action_split_direction_e);
|
||||||
void ghostty_surface_split_focus(ghostty_surface_t,
|
void ghostty_surface_split_focus(ghostty_surface_t,
|
||||||
ghostty_split_focus_direction_e);
|
ghostty_action_goto_split_e);
|
||||||
void ghostty_surface_split_resize(ghostty_surface_t,
|
void ghostty_surface_split_resize(ghostty_surface_t,
|
||||||
ghostty_split_resize_direction_e,
|
ghostty_action_resize_split_direction_e,
|
||||||
uint16_t);
|
uint16_t);
|
||||||
void ghostty_surface_split_equalize(ghostty_surface_t);
|
void ghostty_surface_split_equalize(ghostty_surface_t);
|
||||||
bool ghostty_surface_binding_action(ghostty_surface_t, const char*, uintptr_t);
|
bool ghostty_surface_binding_action(ghostty_surface_t, const char*, uintptr_t);
|
||||||
|
@ -484,6 +484,24 @@ class AppDelegate: NSObject,
|
|||||||
dockMenu.addItem(newTab)
|
dockMenu.addItem(newTab)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//MARK: - Global State
|
||||||
|
|
||||||
|
func setSecureInput(_ mode: Ghostty.SetSecureInput) {
|
||||||
|
let input = SecureInput.shared
|
||||||
|
switch (mode) {
|
||||||
|
case .on:
|
||||||
|
input.global = true
|
||||||
|
|
||||||
|
case .off:
|
||||||
|
input.global = false
|
||||||
|
|
||||||
|
case .toggle:
|
||||||
|
input.global.toggle()
|
||||||
|
}
|
||||||
|
self.menuSecureInput?.state = if (input.global) { .on } else { .off }
|
||||||
|
UserDefaults.standard.set(input.global, forKey: "SecureInput")
|
||||||
|
}
|
||||||
|
|
||||||
//MARK: - IB Actions
|
//MARK: - IB Actions
|
||||||
|
|
||||||
@IBAction func openConfig(_ sender: Any?) {
|
@IBAction func openConfig(_ sender: Any?) {
|
||||||
@ -525,9 +543,6 @@ class AppDelegate: NSObject,
|
|||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func toggleSecureInput(_ sender: Any) {
|
@IBAction func toggleSecureInput(_ sender: Any) {
|
||||||
let input = SecureInput.shared
|
setSecureInput(.toggle)
|
||||||
input.global.toggle()
|
|
||||||
self.menuSecureInput?.state = if (input.global) { .on } else { .off }
|
|
||||||
UserDefaults.standard.set(input.global, forKey: "SecureInput")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -551,12 +551,12 @@ class TerminalController: NSWindowController, NSWindowDelegate,
|
|||||||
|
|
||||||
@IBAction func splitRight(_ sender: Any) {
|
@IBAction func splitRight(_ sender: Any) {
|
||||||
guard let surface = focusedSurface?.surface else { return }
|
guard let surface = focusedSurface?.surface else { return }
|
||||||
ghostty.split(surface: surface, direction: GHOSTTY_SPLIT_RIGHT)
|
ghostty.split(surface: surface, direction: GHOSTTY_SPLIT_DIRECTION_RIGHT)
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func splitDown(_ sender: Any) {
|
@IBAction func splitDown(_ sender: Any) {
|
||||||
guard let surface = focusedSurface?.surface else { return }
|
guard let surface = focusedSurface?.surface else { return }
|
||||||
ghostty.split(surface: surface, direction: GHOSTTY_SPLIT_DOWN)
|
ghostty.split(surface: surface, direction: GHOSTTY_SPLIT_DIRECTION_DOWN)
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func splitZoom(_ sender: Any) {
|
@IBAction func splitZoom(_ sender: Any) {
|
||||||
@ -732,8 +732,9 @@ class TerminalController: NSWindowController, NSWindowDelegate,
|
|||||||
guard let window = self.window else { return }
|
guard let window = self.window else { return }
|
||||||
|
|
||||||
// Get the tab index from the notification
|
// Get the tab index from the notification
|
||||||
guard let tabIndexAny = notification.userInfo?[Ghostty.Notification.GotoTabKey] else { return }
|
guard let tabEnumAny = notification.userInfo?[Ghostty.Notification.GotoTabKey] else { return }
|
||||||
guard let tabIndex = tabIndexAny as? Int32 else { return }
|
guard let tabEnum = tabEnumAny as? ghostty_action_goto_tab_e else { return }
|
||||||
|
let tabIndex: Int32 = .init(bitPattern: tabEnum.rawValue)
|
||||||
|
|
||||||
guard let windowController = window.windowController else { return }
|
guard let windowController = window.windowController else { return }
|
||||||
guard let tabGroup = windowController.window?.tabGroup else { return }
|
guard let tabGroup = windowController.window?.tabGroup else { return }
|
||||||
@ -747,19 +748,19 @@ class TerminalController: NSWindowController, NSWindowDelegate,
|
|||||||
guard let selectedWindow = tabGroup.selectedWindow else { return }
|
guard let selectedWindow = tabGroup.selectedWindow else { return }
|
||||||
guard let selectedIndex = tabbedWindows.firstIndex(where: { $0 == selectedWindow }) else { return }
|
guard let selectedIndex = tabbedWindows.firstIndex(where: { $0 == selectedWindow }) else { return }
|
||||||
|
|
||||||
if (tabIndex == GHOSTTY_TAB_PREVIOUS.rawValue) {
|
if (tabIndex == GHOSTTY_GOTO_TAB_PREVIOUS.rawValue) {
|
||||||
if (selectedIndex == 0) {
|
if (selectedIndex == 0) {
|
||||||
finalIndex = tabbedWindows.count - 1
|
finalIndex = tabbedWindows.count - 1
|
||||||
} else {
|
} else {
|
||||||
finalIndex = selectedIndex - 1
|
finalIndex = selectedIndex - 1
|
||||||
}
|
}
|
||||||
} else if (tabIndex == GHOSTTY_TAB_NEXT.rawValue) {
|
} else if (tabIndex == GHOSTTY_GOTO_TAB_NEXT.rawValue) {
|
||||||
if (selectedIndex == tabbedWindows.count - 1) {
|
if (selectedIndex == tabbedWindows.count - 1) {
|
||||||
finalIndex = 0
|
finalIndex = 0
|
||||||
} else {
|
} else {
|
||||||
finalIndex = selectedIndex + 1
|
finalIndex = selectedIndex + 1
|
||||||
}
|
}
|
||||||
} else if (tabIndex == GHOSTTY_TAB_LAST.rawValue) {
|
} else if (tabIndex == GHOSTTY_GOTO_TAB_LAST.rawValue) {
|
||||||
finalIndex = tabbedWindows.count - 1
|
finalIndex = tabbedWindows.count - 1
|
||||||
} else {
|
} else {
|
||||||
return
|
return
|
||||||
@ -783,9 +784,9 @@ class TerminalController: NSWindowController, NSWindowDelegate,
|
|||||||
guard let window = self.window else { return }
|
guard let window = self.window else { return }
|
||||||
|
|
||||||
// Check whether we use non-native fullscreen
|
// Check whether we use non-native fullscreen
|
||||||
guard let useNonNativeFullscreenAny = notification.userInfo?[Ghostty.Notification.NonNativeFullscreenKey] else { return }
|
guard let fullscreenModeAny = notification.userInfo?[Ghostty.Notification.FullscreenModeKey] else { return }
|
||||||
guard let useNonNativeFullscreen = useNonNativeFullscreenAny as? ghostty_non_native_fullscreen_e else { return }
|
guard let fullscreenMode = fullscreenModeAny as? ghostty_action_fullscreen_e else { return }
|
||||||
self.fullscreenHandler.toggleFullscreen(window: window, nonNativeFullscreen: useNonNativeFullscreen)
|
self.fullscreenHandler.toggleFullscreen(window: window, mode: fullscreenMode)
|
||||||
|
|
||||||
// For some reason focus always gets lost when we toggle fullscreen, so we set it back.
|
// For some reason focus always gets lost when we toggle fullscreen, so we set it back.
|
||||||
if let focusedSurface {
|
if let focusedSurface {
|
||||||
|
@ -67,36 +67,12 @@ extension Ghostty {
|
|||||||
userdata: Unmanaged.passUnretained(self).toOpaque(),
|
userdata: Unmanaged.passUnretained(self).toOpaque(),
|
||||||
supports_selection_clipboard: false,
|
supports_selection_clipboard: false,
|
||||||
wakeup_cb: { userdata in App.wakeup(userdata) },
|
wakeup_cb: { userdata in App.wakeup(userdata) },
|
||||||
|
action_cb: { app, target, action in App.action(app!, target: target, action: action) },
|
||||||
reload_config_cb: { userdata in App.reloadConfig(userdata) },
|
reload_config_cb: { userdata in App.reloadConfig(userdata) },
|
||||||
open_config_cb: { userdata in App.openConfig(userdata) },
|
|
||||||
set_title_cb: { userdata, title in App.setTitle(userdata, title: title) },
|
|
||||||
set_mouse_shape_cb: { userdata, shape in App.setMouseShape(userdata, shape: shape) },
|
|
||||||
set_mouse_visibility_cb: { userdata, visible in App.setMouseVisibility(userdata, visible: visible) },
|
|
||||||
read_clipboard_cb: { userdata, loc, state in App.readClipboard(userdata, location: loc, state: state) },
|
read_clipboard_cb: { userdata, loc, state in App.readClipboard(userdata, location: loc, state: state) },
|
||||||
confirm_read_clipboard_cb: { userdata, str, state, request in App.confirmReadClipboard(userdata, string: str, state: state, request: request ) },
|
confirm_read_clipboard_cb: { userdata, str, state, request in App.confirmReadClipboard(userdata, string: str, state: state, request: request ) },
|
||||||
write_clipboard_cb: { userdata, str, loc, confirm in App.writeClipboard(userdata, string: str, location: loc, confirm: confirm) },
|
write_clipboard_cb: { userdata, str, loc, confirm in App.writeClipboard(userdata, string: str, location: loc, confirm: confirm) },
|
||||||
new_split_cb: { userdata, direction, surfaceConfig in App.newSplit(userdata, direction: direction, config: surfaceConfig) },
|
close_surface_cb: { userdata, processAlive in App.closeSurface(userdata, processAlive: processAlive) }
|
||||||
new_tab_cb: { userdata, surfaceConfig in App.newTab(userdata, config: surfaceConfig) },
|
|
||||||
new_window_cb: { userdata, surfaceConfig in App.newWindow(userdata, config: surfaceConfig) },
|
|
||||||
control_inspector_cb: { userdata, mode in App.controlInspector(userdata, mode: mode) },
|
|
||||||
close_surface_cb: { userdata, processAlive in App.closeSurface(userdata, processAlive: processAlive) },
|
|
||||||
focus_split_cb: { userdata, direction in App.focusSplit(userdata, direction: direction) },
|
|
||||||
resize_split_cb: { userdata, direction, amount in
|
|
||||||
App.resizeSplit(userdata, direction: direction, amount: amount) },
|
|
||||||
equalize_splits_cb: { userdata in
|
|
||||||
App.equalizeSplits(userdata) },
|
|
||||||
toggle_split_zoom_cb: { userdata in App.toggleSplitZoom(userdata) },
|
|
||||||
goto_tab_cb: { userdata, n in App.gotoTab(userdata, n: n) },
|
|
||||||
toggle_fullscreen_cb: { userdata, nonNativeFullscreen in App.toggleFullscreen(userdata, nonNativeFullscreen: nonNativeFullscreen) },
|
|
||||||
set_initial_window_size_cb: { userdata, width, height in App.setInitialWindowSize(userdata, width: width, height: height) },
|
|
||||||
render_inspector_cb: { userdata in App.renderInspector(userdata) },
|
|
||||||
set_cell_size_cb: { userdata, width, height in App.setCellSize(userdata, width: width, height: height) },
|
|
||||||
show_desktop_notification_cb: { userdata, title, body in
|
|
||||||
App.showUserNotification(userdata, title: title, body: body) },
|
|
||||||
update_renderer_health_cb: { userdata, health in App.updateRendererHealth(userdata, health: health) },
|
|
||||||
mouse_over_link_cb: { userdata, ptr, len in App.mouseOverLink(userdata, uri: ptr, len: len) },
|
|
||||||
set_password_input_cb: { userdata, value in App.setPasswordInput(userdata, value: value) },
|
|
||||||
toggle_secure_input_cb: { App.toggleSecureInput() }
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Create the ghostty app.
|
// Create the ghostty app.
|
||||||
@ -185,7 +161,7 @@ extension Ghostty {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func split(surface: ghostty_surface_t, direction: ghostty_split_direction_e) {
|
func split(surface: ghostty_surface_t, direction: ghostty_action_split_direction_e) {
|
||||||
ghostty_surface_split(surface, direction)
|
ghostty_surface_split(surface, direction)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -254,11 +230,8 @@ extension Ghostty {
|
|||||||
// MARK: Ghostty Callbacks (iOS)
|
// MARK: Ghostty Callbacks (iOS)
|
||||||
|
|
||||||
static func wakeup(_ userdata: UnsafeMutableRawPointer?) {}
|
static func wakeup(_ userdata: UnsafeMutableRawPointer?) {}
|
||||||
|
static func action(_ app: ghostty_app_t, target: ghostty_target_s, action: ghostty_action_s) {}
|
||||||
static func reloadConfig(_ userdata: UnsafeMutableRawPointer?) -> ghostty_config_t? { return nil }
|
static func reloadConfig(_ userdata: UnsafeMutableRawPointer?) -> ghostty_config_t? { return nil }
|
||||||
static func openConfig(_ userdata: UnsafeMutableRawPointer?) {}
|
|
||||||
static func setTitle(_ userdata: UnsafeMutableRawPointer?, title: UnsafePointer<CChar>?) {}
|
|
||||||
static func setMouseShape(_ userdata: UnsafeMutableRawPointer?, shape: ghostty_mouse_shape_e) {}
|
|
||||||
static func setMouseVisibility(_ userdata: UnsafeMutableRawPointer?, visible: Bool) {}
|
|
||||||
static func readClipboard(
|
static func readClipboard(
|
||||||
_ userdata: UnsafeMutableRawPointer?,
|
_ userdata: UnsafeMutableRawPointer?,
|
||||||
location: ghostty_clipboard_e,
|
location: ghostty_clipboard_e,
|
||||||
@ -279,30 +252,7 @@ extension Ghostty {
|
|||||||
confirm: Bool
|
confirm: Bool
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
static func newSplit(
|
|
||||||
_ userdata: UnsafeMutableRawPointer?,
|
|
||||||
direction: ghostty_split_direction_e,
|
|
||||||
config: ghostty_surface_config_s
|
|
||||||
) {}
|
|
||||||
|
|
||||||
static func newTab(_ userdata: UnsafeMutableRawPointer?, config: ghostty_surface_config_s) {}
|
|
||||||
static func newWindow(_ userdata: UnsafeMutableRawPointer?, config: ghostty_surface_config_s) {}
|
|
||||||
static func controlInspector(_ userdata: UnsafeMutableRawPointer?, mode: ghostty_inspector_mode_e) {}
|
|
||||||
static func closeSurface(_ userdata: UnsafeMutableRawPointer?, processAlive: Bool) {}
|
static func closeSurface(_ userdata: UnsafeMutableRawPointer?, processAlive: Bool) {}
|
||||||
static func focusSplit(_ userdata: UnsafeMutableRawPointer?, direction: ghostty_split_focus_direction_e) {}
|
|
||||||
static func resizeSplit(_ userdata: UnsafeMutableRawPointer?, direction: ghostty_split_resize_direction_e, amount: UInt16) {}
|
|
||||||
static func equalizeSplits(_ userdata: UnsafeMutableRawPointer?) {}
|
|
||||||
static func toggleSplitZoom(_ userdata: UnsafeMutableRawPointer?) {}
|
|
||||||
static func gotoTab(_ userdata: UnsafeMutableRawPointer?, n: Int32) {}
|
|
||||||
static func toggleFullscreen(_ userdata: UnsafeMutableRawPointer?, nonNativeFullscreen: ghostty_non_native_fullscreen_e) {}
|
|
||||||
static func setInitialWindowSize(_ userdata: UnsafeMutableRawPointer?, width: UInt32, height: UInt32) {}
|
|
||||||
static func renderInspector(_ userdata: UnsafeMutableRawPointer?) {}
|
|
||||||
static func setCellSize(_ userdata: UnsafeMutableRawPointer?, width: UInt32, height: UInt32) {}
|
|
||||||
static func showUserNotification(_ userdata: UnsafeMutableRawPointer?, title: UnsafePointer<CChar>?, body: UnsafePointer<CChar>?) {}
|
|
||||||
static func updateRendererHealth(_ userdata: UnsafeMutableRawPointer?, health: ghostty_renderer_health_e) {}
|
|
||||||
static func mouseOverLink(_ userdata: UnsafeMutableRawPointer?, uri: UnsafePointer<CChar>?, len: Int) {}
|
|
||||||
static func setPasswordInput(_ userdata: UnsafeMutableRawPointer?, value: Bool) {}
|
|
||||||
static func toggleSecureInput() {}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
@ -318,14 +268,6 @@ extension Ghostty {
|
|||||||
|
|
||||||
// MARK: Ghostty Callbacks (macOS)
|
// MARK: Ghostty Callbacks (macOS)
|
||||||
|
|
||||||
static func newSplit(_ userdata: UnsafeMutableRawPointer?, direction: ghostty_split_direction_e, config: ghostty_surface_config_s) {
|
|
||||||
let surface = self.surfaceUserdata(from: userdata)
|
|
||||||
NotificationCenter.default.post(name: Notification.ghosttyNewSplit, object: surface, userInfo: [
|
|
||||||
"direction": direction,
|
|
||||||
Notification.NewSurfaceConfigKey: SurfaceConfiguration(from: config),
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
static func closeSurface(_ userdata: UnsafeMutableRawPointer?, processAlive: Bool) {
|
static func closeSurface(_ userdata: UnsafeMutableRawPointer?, processAlive: Bool) {
|
||||||
let surface = self.surfaceUserdata(from: userdata)
|
let surface = self.surfaceUserdata(from: userdata)
|
||||||
NotificationCenter.default.post(name: Notification.ghosttyCloseSurface, object: surface, userInfo: [
|
NotificationCenter.default.post(name: Notification.ghosttyCloseSurface, object: surface, userInfo: [
|
||||||
@ -333,56 +275,6 @@ extension Ghostty {
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
static func focusSplit(_ userdata: UnsafeMutableRawPointer?, direction: ghostty_split_focus_direction_e) {
|
|
||||||
let surface = self.surfaceUserdata(from: userdata)
|
|
||||||
guard let splitDirection = SplitFocusDirection.from(direction: direction) else { return }
|
|
||||||
NotificationCenter.default.post(
|
|
||||||
name: Notification.ghosttyFocusSplit,
|
|
||||||
object: surface,
|
|
||||||
userInfo: [
|
|
||||||
Notification.SplitDirectionKey: splitDirection,
|
|
||||||
]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
static func resizeSplit(_ userdata: UnsafeMutableRawPointer?, direction: ghostty_split_resize_direction_e, amount: UInt16) {
|
|
||||||
let surface = self.surfaceUserdata(from: userdata)
|
|
||||||
guard let resizeDirection = SplitResizeDirection.from(direction: direction) else { return }
|
|
||||||
NotificationCenter.default.post(
|
|
||||||
name: Notification.didResizeSplit,
|
|
||||||
object: surface,
|
|
||||||
userInfo: [
|
|
||||||
Notification.ResizeSplitDirectionKey: resizeDirection,
|
|
||||||
Notification.ResizeSplitAmountKey: amount,
|
|
||||||
]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
static func equalizeSplits(_ userdata: UnsafeMutableRawPointer?) {
|
|
||||||
let surface = self.surfaceUserdata(from: userdata)
|
|
||||||
NotificationCenter.default.post(name: Notification.didEqualizeSplits, object: surface)
|
|
||||||
}
|
|
||||||
|
|
||||||
static func toggleSplitZoom(_ userdata: UnsafeMutableRawPointer?) {
|
|
||||||
let surface = self.surfaceUserdata(from: userdata)
|
|
||||||
|
|
||||||
NotificationCenter.default.post(
|
|
||||||
name: Notification.didToggleSplitZoom,
|
|
||||||
object: surface
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
static func gotoTab(_ userdata: UnsafeMutableRawPointer?, n: Int32) {
|
|
||||||
let surface = self.surfaceUserdata(from: userdata)
|
|
||||||
NotificationCenter.default.post(
|
|
||||||
name: Notification.ghosttyGotoTab,
|
|
||||||
object: surface,
|
|
||||||
userInfo: [
|
|
||||||
Notification.GotoTabKey: n,
|
|
||||||
]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
static func readClipboard(_ userdata: UnsafeMutableRawPointer?, location: ghostty_clipboard_e, state: UnsafeMutableRawPointer?) {
|
static func readClipboard(_ userdata: UnsafeMutableRawPointer?, location: ghostty_clipboard_e, state: UnsafeMutableRawPointer?) {
|
||||||
// If we don't even have a surface, something went terrible wrong so we have
|
// If we don't even have a surface, something went terrible wrong so we have
|
||||||
// to leak "state".
|
// to leak "state".
|
||||||
@ -454,10 +346,6 @@ extension Ghostty {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
static func openConfig(_ userdata: UnsafeMutableRawPointer?) {
|
|
||||||
ghostty_config_open();
|
|
||||||
}
|
|
||||||
|
|
||||||
static func reloadConfig(_ userdata: UnsafeMutableRawPointer?) -> ghostty_config_t? {
|
static func reloadConfig(_ userdata: UnsafeMutableRawPointer?) -> ghostty_config_t? {
|
||||||
let newConfig = Config()
|
let newConfig = Config()
|
||||||
guard newConfig.loaded else {
|
guard newConfig.loaded else {
|
||||||
@ -488,90 +376,410 @@ extension Ghostty {
|
|||||||
DispatchQueue.main.async { state.appTick() }
|
DispatchQueue.main.async { state.appTick() }
|
||||||
}
|
}
|
||||||
|
|
||||||
static func renderInspector(_ userdata: UnsafeMutableRawPointer?) {
|
/// Determine if a given notification should be presented to the user when Ghostty is running in the foreground.
|
||||||
let surface = self.surfaceUserdata(from: userdata)
|
func shouldPresentNotification(notification: UNNotification) -> Bool {
|
||||||
NotificationCenter.default.post(
|
let userInfo = notification.request.content.userInfo
|
||||||
name: Notification.inspectorNeedsDisplay,
|
guard let uuidString = userInfo["surface"] as? String,
|
||||||
object: surface
|
let uuid = UUID(uuidString: uuidString),
|
||||||
)
|
let surface = delegate?.findSurface(forUUID: uuid),
|
||||||
|
let window = surface.window else { return false }
|
||||||
|
return !window.isKeyWindow || !surface.focused
|
||||||
}
|
}
|
||||||
|
|
||||||
static func setTitle(_ userdata: UnsafeMutableRawPointer?, title: UnsafePointer<CChar>?) {
|
/// Returns the GhosttyState from the given userdata value.
|
||||||
let surfaceView = self.surfaceUserdata(from: userdata)
|
static private func appState(fromView view: SurfaceView) -> App? {
|
||||||
guard let titleStr = String(cString: title!, encoding: .utf8) else { return }
|
guard let surface = view.surface else { return nil }
|
||||||
DispatchQueue.main.async {
|
guard let app = ghostty_surface_app(surface) else { return nil }
|
||||||
surfaceView.title = titleStr
|
guard let app_ud = ghostty_app_userdata(app) else { return nil }
|
||||||
}
|
return Unmanaged<App>.fromOpaque(app_ud).takeUnretainedValue()
|
||||||
}
|
}
|
||||||
|
|
||||||
static func setMouseShape(_ userdata: UnsafeMutableRawPointer?, shape: ghostty_mouse_shape_e) {
|
/// Returns the surface view from the userdata.
|
||||||
let surfaceView = self.surfaceUserdata(from: userdata)
|
static private func surfaceUserdata(from userdata: UnsafeMutableRawPointer?) -> SurfaceView {
|
||||||
surfaceView.setCursorShape(shape)
|
return Unmanaged<SurfaceView>.fromOpaque(userdata!).takeUnretainedValue()
|
||||||
}
|
}
|
||||||
|
|
||||||
static func setMouseVisibility(_ userdata: UnsafeMutableRawPointer?, visible: Bool) {
|
static private func surfaceView(from surface: ghostty_surface_t) -> SurfaceView? {
|
||||||
let surfaceView = self.surfaceUserdata(from: userdata)
|
guard let surface_ud = ghostty_surface_userdata(surface) else { return nil }
|
||||||
surfaceView.setCursorVisibility(visible)
|
return Unmanaged<SurfaceView>.fromOpaque(surface_ud).takeUnretainedValue()
|
||||||
}
|
}
|
||||||
|
|
||||||
static func toggleFullscreen(_ userdata: UnsafeMutableRawPointer?, nonNativeFullscreen: ghostty_non_native_fullscreen_e) {
|
// MARK: Actions (macOS)
|
||||||
let surface = self.surfaceUserdata(from: userdata)
|
|
||||||
NotificationCenter.default.post(
|
|
||||||
name: Notification.ghosttyToggleFullscreen,
|
|
||||||
object: surface,
|
|
||||||
userInfo: [
|
|
||||||
Notification.NonNativeFullscreenKey: nonNativeFullscreen,
|
|
||||||
]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
static func setInitialWindowSize(_ userdata: UnsafeMutableRawPointer?, width: UInt32, height: UInt32) {
|
static func action(_ app: ghostty_app_t, target: ghostty_target_s, action: ghostty_action_s) {
|
||||||
// We need a window to set the frame
|
// Make sure it a target we understand so all our action handlers can assert
|
||||||
let surfaceView = self.surfaceUserdata(from: userdata)
|
switch (target.tag) {
|
||||||
surfaceView.initialSize = NSMakeSize(Double(width), Double(height))
|
case GHOSTTY_TARGET_APP, GHOSTTY_TARGET_SURFACE:
|
||||||
}
|
break
|
||||||
|
|
||||||
static func setCellSize(_ userdata: UnsafeMutableRawPointer?, width: UInt32, height: UInt32) {
|
default:
|
||||||
let surfaceView = self.surfaceUserdata(from: userdata)
|
Ghostty.logger.warning("unknown action target=\(target.tag.rawValue)")
|
||||||
let backingSize = NSSize(width: Double(width), height: Double(height))
|
|
||||||
surfaceView.cellSize = surfaceView.convertFromBacking(backingSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
static func mouseOverLink(_ userdata: UnsafeMutableRawPointer?, uri: UnsafePointer<CChar>?, len: Int) {
|
|
||||||
let surfaceView = self.surfaceUserdata(from: userdata)
|
|
||||||
guard len > 0 else {
|
|
||||||
surfaceView.hoverUrl = nil
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let buffer = Data(bytes: uri!, count: len)
|
// Action dispatch
|
||||||
surfaceView.hoverUrl = String(data: buffer, encoding: .utf8)
|
switch (action.tag) {
|
||||||
|
case GHOSTTY_ACTION_NEW_WINDOW:
|
||||||
|
newWindow(app, target: target)
|
||||||
|
|
||||||
|
case GHOSTTY_ACTION_NEW_TAB:
|
||||||
|
newTab(app, target: target)
|
||||||
|
|
||||||
|
case GHOSTTY_ACTION_NEW_SPLIT:
|
||||||
|
newSplit(app, target: target, direction: action.action.new_split)
|
||||||
|
|
||||||
|
case GHOSTTY_ACTION_TOGGLE_FULLSCREEN:
|
||||||
|
toggleFullscreen(app, target: target, mode: action.action.toggle_fullscreen)
|
||||||
|
|
||||||
|
case GHOSTTY_ACTION_GOTO_TAB:
|
||||||
|
gotoTab(app, target: target, tab: action.action.goto_tab)
|
||||||
|
|
||||||
|
case GHOSTTY_ACTION_GOTO_SPLIT:
|
||||||
|
gotoSplit(app, target: target, direction: action.action.goto_split)
|
||||||
|
|
||||||
|
case GHOSTTY_ACTION_RESIZE_SPLIT:
|
||||||
|
resizeSplit(app, target: target, resize: action.action.resize_split)
|
||||||
|
|
||||||
|
case GHOSTTY_ACTION_EQUALIZE_SPLITS:
|
||||||
|
equalizeSplits(app, target: target)
|
||||||
|
|
||||||
|
case GHOSTTY_ACTION_TOGGLE_SPLIT_ZOOM:
|
||||||
|
toggleSplitZoom(app, target: target)
|
||||||
|
|
||||||
|
case GHOSTTY_ACTION_INSPECTOR:
|
||||||
|
controlInspector(app, target: target, mode: action.action.inspector)
|
||||||
|
|
||||||
|
case GHOSTTY_ACTION_RENDER_INSPECTOR:
|
||||||
|
renderInspector(app, target: target)
|
||||||
|
|
||||||
|
case GHOSTTY_ACTION_DESKTOP_NOTIFICATION:
|
||||||
|
showDesktopNotification(app, target: target, n: action.action.desktop_notification)
|
||||||
|
|
||||||
|
case GHOSTTY_ACTION_SET_TITLE:
|
||||||
|
setTitle(app, target: target, v: action.action.set_title)
|
||||||
|
|
||||||
|
case GHOSTTY_ACTION_OPEN_CONFIG:
|
||||||
|
ghostty_config_open()
|
||||||
|
|
||||||
|
case GHOSTTY_ACTION_SECURE_INPUT:
|
||||||
|
toggleSecureInput(app, target: target, mode: action.action.secure_input)
|
||||||
|
|
||||||
|
case GHOSTTY_ACTION_MOUSE_SHAPE:
|
||||||
|
setMouseShape(app, target: target, shape: action.action.mouse_shape)
|
||||||
|
|
||||||
|
case GHOSTTY_ACTION_MOUSE_VISIBILITY:
|
||||||
|
setMouseVisibility(app, target: target, v: action.action.mouse_visibility)
|
||||||
|
|
||||||
|
case GHOSTTY_ACTION_MOUSE_OVER_LINK:
|
||||||
|
setMouseOverLink(app, target: target, v: action.action.mouse_over_link)
|
||||||
|
|
||||||
|
case GHOSTTY_ACTION_INITIAL_SIZE:
|
||||||
|
setInitialSize(app, target: target, v: action.action.initial_size)
|
||||||
|
|
||||||
|
case GHOSTTY_ACTION_CELL_SIZE:
|
||||||
|
setCellSize(app, target: target, v: action.action.cell_size)
|
||||||
|
|
||||||
|
case GHOSTTY_ACTION_RENDERER_HEALTH:
|
||||||
|
rendererHealth(app, target: target, v: action.action.renderer_health)
|
||||||
|
|
||||||
|
case GHOSTTY_ACTION_CLOSE_ALL_WINDOWS:
|
||||||
|
fallthrough
|
||||||
|
case GHOSTTY_ACTION_TOGGLE_WINDOW_DECORATIONS:
|
||||||
|
fallthrough
|
||||||
|
case GHOSTTY_ACTION_PRESENT_TERMINAL:
|
||||||
|
fallthrough
|
||||||
|
case GHOSTTY_ACTION_SIZE_LIMIT:
|
||||||
|
fallthrough
|
||||||
|
case GHOSTTY_ACTION_QUIT_TIMER:
|
||||||
|
Ghostty.logger.info("known but unimplemented action action=\(action.tag.rawValue)")
|
||||||
|
|
||||||
|
default:
|
||||||
|
Ghostty.logger.warning("unknown action action=\(action.tag.rawValue)")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static func setPasswordInput(_ userdata: UnsafeMutableRawPointer?, value: Bool) {
|
private static func newWindow(_ app: ghostty_app_t, target: ghostty_target_s) {
|
||||||
// We don't currently allow global password input being set from this.
|
switch (target.tag) {
|
||||||
guard let userdata else { return }
|
case GHOSTTY_TARGET_APP:
|
||||||
|
NotificationCenter.default.post(
|
||||||
|
name: Notification.ghosttyNewWindow,
|
||||||
|
object: nil,
|
||||||
|
userInfo: [:]
|
||||||
|
)
|
||||||
|
|
||||||
let surfaceView = self.surfaceUserdata(from: userdata)
|
case GHOSTTY_TARGET_SURFACE:
|
||||||
|
guard let surface = target.target.surface else { return }
|
||||||
|
guard let surfaceView = self.surfaceView(from: surface) else { return }
|
||||||
|
NotificationCenter.default.post(
|
||||||
|
name: Notification.ghosttyNewWindow,
|
||||||
|
object: surfaceView,
|
||||||
|
userInfo: [
|
||||||
|
Notification.NewSurfaceConfigKey: SurfaceConfiguration(from: ghostty_surface_inherited_config(surface)),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
default:
|
||||||
|
assertionFailure()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func newTab(_ app: ghostty_app_t, target: ghostty_target_s) {
|
||||||
|
switch (target.tag) {
|
||||||
|
case GHOSTTY_TARGET_APP:
|
||||||
|
NotificationCenter.default.post(
|
||||||
|
name: Notification.ghosttyNewTab,
|
||||||
|
object: nil,
|
||||||
|
userInfo: [:]
|
||||||
|
)
|
||||||
|
|
||||||
|
case GHOSTTY_TARGET_SURFACE:
|
||||||
|
guard let surface = target.target.surface else { return }
|
||||||
|
guard let surfaceView = self.surfaceView(from: surface) else { return }
|
||||||
guard let appState = self.appState(fromView: surfaceView) else { return }
|
guard let appState = self.appState(fromView: surfaceView) else { return }
|
||||||
guard appState.config.autoSecureInput else { return }
|
guard appState.config.windowDecorations else {
|
||||||
surfaceView.passwordInput = value
|
let alert = NSAlert()
|
||||||
|
alert.messageText = "Tabs are disabled"
|
||||||
|
alert.informativeText = "Enable window decorations to use tabs"
|
||||||
|
alert.addButton(withTitle: "OK")
|
||||||
|
alert.alertStyle = .warning
|
||||||
|
_ = alert.runModal()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
static func toggleSecureInput() {
|
NotificationCenter.default.post(
|
||||||
guard let appDelegate = NSApplication.shared.delegate as? AppDelegate else { return }
|
name: Notification.ghosttyNewTab,
|
||||||
appDelegate.toggleSecureInput(self)
|
object: surfaceView,
|
||||||
|
userInfo: [
|
||||||
|
Notification.NewSurfaceConfigKey: SurfaceConfiguration(from: ghostty_surface_inherited_config(surface)),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
default:
|
||||||
|
assertionFailure()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static func showUserNotification(_ userdata: UnsafeMutableRawPointer?, title: UnsafePointer<CChar>?, body: UnsafePointer<CChar>?) {
|
private static func newSplit(
|
||||||
let surfaceView = self.surfaceUserdata(from: userdata)
|
_ app: ghostty_app_t,
|
||||||
guard let title = String(cString: title!, encoding: .utf8) else { return }
|
target: ghostty_target_s,
|
||||||
guard let body = String(cString: body!, encoding: .utf8) else { return }
|
direction: ghostty_action_split_direction_e) {
|
||||||
|
switch (target.tag) {
|
||||||
|
case GHOSTTY_TARGET_APP:
|
||||||
|
// New split does nothing with an app target
|
||||||
|
Ghostty.logger.warning("new split 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 }
|
||||||
|
|
||||||
|
NotificationCenter.default.post(
|
||||||
|
name: Notification.ghosttyNewSplit,
|
||||||
|
object: surfaceView,
|
||||||
|
userInfo: [
|
||||||
|
"direction": direction,
|
||||||
|
Notification.NewSurfaceConfigKey: SurfaceConfiguration(from: ghostty_surface_inherited_config(surface)),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
default:
|
||||||
|
assertionFailure()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func toggleFullscreen(
|
||||||
|
_ app: ghostty_app_t,
|
||||||
|
target: ghostty_target_s,
|
||||||
|
mode: ghostty_action_fullscreen_e) {
|
||||||
|
switch (target.tag) {
|
||||||
|
case GHOSTTY_TARGET_APP:
|
||||||
|
Ghostty.logger.warning("toggle fullscreen 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 }
|
||||||
|
NotificationCenter.default.post(
|
||||||
|
name: Notification.ghosttyToggleFullscreen,
|
||||||
|
object: surfaceView,
|
||||||
|
userInfo: [
|
||||||
|
Notification.FullscreenModeKey: mode,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
default:
|
||||||
|
assertionFailure()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func gotoTab(
|
||||||
|
_ app: ghostty_app_t,
|
||||||
|
target: ghostty_target_s,
|
||||||
|
tab: ghostty_action_goto_tab_e) {
|
||||||
|
switch (target.tag) {
|
||||||
|
case GHOSTTY_TARGET_APP:
|
||||||
|
Ghostty.logger.warning("goto tab 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 }
|
||||||
|
NotificationCenter.default.post(
|
||||||
|
name: Notification.ghosttyGotoTab,
|
||||||
|
object: surfaceView,
|
||||||
|
userInfo: [
|
||||||
|
Notification.GotoTabKey: tab,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
default:
|
||||||
|
assertionFailure()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func gotoSplit(
|
||||||
|
_ app: ghostty_app_t,
|
||||||
|
target: ghostty_target_s,
|
||||||
|
direction: ghostty_action_goto_split_e) {
|
||||||
|
switch (target.tag) {
|
||||||
|
case GHOSTTY_TARGET_APP:
|
||||||
|
Ghostty.logger.warning("goto split 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 }
|
||||||
|
NotificationCenter.default.post(
|
||||||
|
name: Notification.ghosttyFocusSplit,
|
||||||
|
object: surfaceView,
|
||||||
|
userInfo: [
|
||||||
|
Notification.SplitDirectionKey: SplitFocusDirection.from(direction: direction) as Any,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
default:
|
||||||
|
assertionFailure()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func resizeSplit(
|
||||||
|
_ app: ghostty_app_t,
|
||||||
|
target: ghostty_target_s,
|
||||||
|
resize: ghostty_action_resize_split_s) {
|
||||||
|
switch (target.tag) {
|
||||||
|
case GHOSTTY_TARGET_APP:
|
||||||
|
Ghostty.logger.warning("resize split 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 }
|
||||||
|
guard let resizeDirection = SplitResizeDirection.from(direction: resize.direction) else { return }
|
||||||
|
NotificationCenter.default.post(
|
||||||
|
name: Notification.didResizeSplit,
|
||||||
|
object: surfaceView,
|
||||||
|
userInfo: [
|
||||||
|
Notification.ResizeSplitDirectionKey: resizeDirection,
|
||||||
|
Notification.ResizeSplitAmountKey: resize.amount,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
default:
|
||||||
|
assertionFailure()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func equalizeSplits(
|
||||||
|
_ app: ghostty_app_t,
|
||||||
|
target: ghostty_target_s) {
|
||||||
|
switch (target.tag) {
|
||||||
|
case GHOSTTY_TARGET_APP:
|
||||||
|
Ghostty.logger.warning("equalize splits 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 }
|
||||||
|
NotificationCenter.default.post(
|
||||||
|
name: Notification.didEqualizeSplits,
|
||||||
|
object: surfaceView
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
default:
|
||||||
|
assertionFailure()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func toggleSplitZoom(
|
||||||
|
_ app: ghostty_app_t,
|
||||||
|
target: ghostty_target_s) {
|
||||||
|
switch (target.tag) {
|
||||||
|
case GHOSTTY_TARGET_APP:
|
||||||
|
Ghostty.logger.warning("toggle split zoom 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 }
|
||||||
|
NotificationCenter.default.post(
|
||||||
|
name: Notification.didToggleSplitZoom,
|
||||||
|
object: surfaceView
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
default:
|
||||||
|
assertionFailure()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func controlInspector(
|
||||||
|
_ app: ghostty_app_t,
|
||||||
|
target: ghostty_target_s,
|
||||||
|
mode: ghostty_action_inspector_e) {
|
||||||
|
switch (target.tag) {
|
||||||
|
case GHOSTTY_TARGET_APP:
|
||||||
|
Ghostty.logger.warning("toggle split zoom 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 }
|
||||||
|
NotificationCenter.default.post(
|
||||||
|
name: Notification.didControlInspector,
|
||||||
|
object: surfaceView,
|
||||||
|
userInfo: ["mode": mode]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
default:
|
||||||
|
assertionFailure()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func showDesktopNotification(
|
||||||
|
_ app: ghostty_app_t,
|
||||||
|
target: ghostty_target_s,
|
||||||
|
n: ghostty_action_desktop_notification_s) {
|
||||||
|
switch (target.tag) {
|
||||||
|
case GHOSTTY_TARGET_APP:
|
||||||
|
Ghostty.logger.warning("toggle split zoom 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 }
|
||||||
|
guard let title = String(cString: n.title!, encoding: .utf8) else { return }
|
||||||
|
guard let body = String(cString: n.body!, encoding: .utf8) else { return }
|
||||||
|
|
||||||
let center = UNUserNotificationCenter.current()
|
let center = UNUserNotificationCenter.current()
|
||||||
center.requestAuthorization(options: [.alert, .sound]) { _, error in
|
center.requestAuthorization(options: [.alert, .sound]) { _, error in
|
||||||
if let error = error {
|
if let error = error {
|
||||||
AppDelegate.logger.error("Error while requesting notification authorization: \(error)")
|
Ghostty.logger.error("Error while requesting notification authorization: \(error)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -579,7 +787,236 @@ extension Ghostty {
|
|||||||
guard settings.authorizationStatus == .authorized else { return }
|
guard settings.authorizationStatus == .authorized else { return }
|
||||||
surfaceView.showUserNotification(title: title, body: body)
|
surfaceView.showUserNotification(title: title, body: body)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
default:
|
||||||
|
assertionFailure()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func toggleSecureInput(
|
||||||
|
_ app: ghostty_app_t,
|
||||||
|
target: ghostty_target_s,
|
||||||
|
mode mode_raw: ghostty_action_secure_input_e
|
||||||
|
) {
|
||||||
|
guard let mode = SetSecureInput.from(mode_raw) else { return }
|
||||||
|
|
||||||
|
switch (target.tag) {
|
||||||
|
case GHOSTTY_TARGET_APP:
|
||||||
|
guard let appDelegate = NSApplication.shared.delegate as? AppDelegate else { return }
|
||||||
|
appDelegate.setSecureInput(mode)
|
||||||
|
|
||||||
|
case GHOSTTY_TARGET_SURFACE:
|
||||||
|
guard let surface = target.target.surface else { return }
|
||||||
|
guard let surfaceView = self.surfaceView(from: surface) else { return }
|
||||||
|
guard let appState = self.appState(fromView: surfaceView) else { return }
|
||||||
|
guard appState.config.autoSecureInput else { return }
|
||||||
|
|
||||||
|
switch (mode) {
|
||||||
|
case .on:
|
||||||
|
surfaceView.passwordInput = true
|
||||||
|
|
||||||
|
case .off:
|
||||||
|
surfaceView.passwordInput = false
|
||||||
|
|
||||||
|
case .toggle:
|
||||||
|
surfaceView.passwordInput = !surfaceView.passwordInput
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
assertionFailure()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func setTitle(
|
||||||
|
_ app: ghostty_app_t,
|
||||||
|
target: ghostty_target_s,
|
||||||
|
v: ghostty_action_set_title_s) {
|
||||||
|
switch (target.tag) {
|
||||||
|
case GHOSTTY_TARGET_APP:
|
||||||
|
Ghostty.logger.warning("set title 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 }
|
||||||
|
guard let title = String(cString: v.title!, encoding: .utf8) else { return }
|
||||||
|
|
||||||
|
// We must set this in a dispatchqueue to avoid a deadlock on startup on some
|
||||||
|
// versions of macOS. I unfortunately didn't document the exact versions so
|
||||||
|
// I don't know when its safe to remove this.
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
surfaceView.title = title
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
default:
|
||||||
|
assertionFailure()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func setMouseShape(
|
||||||
|
_ app: ghostty_app_t,
|
||||||
|
target: ghostty_target_s,
|
||||||
|
shape: ghostty_action_mouse_shape_e) {
|
||||||
|
switch (target.tag) {
|
||||||
|
case GHOSTTY_TARGET_APP:
|
||||||
|
Ghostty.logger.warning("set mouse shapes 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.setCursorShape(shape)
|
||||||
|
|
||||||
|
|
||||||
|
default:
|
||||||
|
assertionFailure()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func setMouseVisibility(
|
||||||
|
_ app: ghostty_app_t,
|
||||||
|
target: ghostty_target_s,
|
||||||
|
v: ghostty_action_mouse_visibility_e) {
|
||||||
|
switch (target.tag) {
|
||||||
|
case GHOSTTY_TARGET_APP:
|
||||||
|
Ghostty.logger.warning("set mouse shapes 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 }
|
||||||
|
switch (v) {
|
||||||
|
case GHOSTTY_MOUSE_VISIBLE:
|
||||||
|
surfaceView.setCursorVisibility(true)
|
||||||
|
|
||||||
|
case GHOSTTY_MOUSE_HIDDEN:
|
||||||
|
surfaceView.setCursorVisibility(false)
|
||||||
|
|
||||||
|
default:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
default:
|
||||||
|
assertionFailure()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func setMouseOverLink(
|
||||||
|
_ app: ghostty_app_t,
|
||||||
|
target: ghostty_target_s,
|
||||||
|
v: ghostty_action_mouse_over_link_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 }
|
||||||
|
guard v.len > 0 else {
|
||||||
|
surfaceView.hoverUrl = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let buffer = Data(bytes: v.url!, count: v.len)
|
||||||
|
surfaceView.hoverUrl = String(data: buffer, encoding: .utf8)
|
||||||
|
|
||||||
|
|
||||||
|
default:
|
||||||
|
assertionFailure()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func setInitialSize(
|
||||||
|
_ app: ghostty_app_t,
|
||||||
|
target: ghostty_target_s,
|
||||||
|
v: ghostty_action_initial_size_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.initialSize = NSMakeSize(Double(v.width), Double(v.height))
|
||||||
|
|
||||||
|
|
||||||
|
default:
|
||||||
|
assertionFailure()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func setCellSize(
|
||||||
|
_ app: ghostty_app_t,
|
||||||
|
target: ghostty_target_s,
|
||||||
|
v: ghostty_action_cell_size_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 }
|
||||||
|
let backingSize = NSSize(width: Double(v.width), height: Double(v.height))
|
||||||
|
surfaceView.cellSize = surfaceView.convertFromBacking(backingSize)
|
||||||
|
|
||||||
|
default:
|
||||||
|
assertionFailure()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func renderInspector(
|
||||||
|
_ app: ghostty_app_t,
|
||||||
|
target: ghostty_target_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 }
|
||||||
|
NotificationCenter.default.post(
|
||||||
|
name: Notification.inspectorNeedsDisplay,
|
||||||
|
object: surfaceView
|
||||||
|
)
|
||||||
|
|
||||||
|
default:
|
||||||
|
assertionFailure()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func rendererHealth(
|
||||||
|
_ app: ghostty_app_t,
|
||||||
|
target: ghostty_target_s,
|
||||||
|
v: ghostty_action_renderer_health_e) {
|
||||||
|
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 }
|
||||||
|
NotificationCenter.default.post(
|
||||||
|
name: Notification.didUpdateRendererHealth,
|
||||||
|
object: surfaceView,
|
||||||
|
userInfo: [
|
||||||
|
"health": v,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
default:
|
||||||
|
assertionFailure()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: User Notifications
|
||||||
|
|
||||||
/// Handle a received user notification. This is called when a user notification is clicked or dismissed by the user
|
/// Handle a received user notification. This is called when a user notification is clicked or dismissed by the user
|
||||||
func handleUserNotification(response: UNNotificationResponse) {
|
func handleUserNotification(response: UNNotificationResponse) {
|
||||||
@ -600,86 +1037,6 @@ extension Ghostty {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Determine if a given notification should be presented to the user when Ghostty is running in the foreground.
|
|
||||||
func shouldPresentNotification(notification: UNNotification) -> Bool {
|
|
||||||
let userInfo = notification.request.content.userInfo
|
|
||||||
guard let uuidString = userInfo["surface"] as? String,
|
|
||||||
let uuid = UUID(uuidString: uuidString),
|
|
||||||
let surface = delegate?.findSurface(forUUID: uuid),
|
|
||||||
let window = surface.window else { return false }
|
|
||||||
return !window.isKeyWindow || !surface.focused
|
|
||||||
}
|
|
||||||
|
|
||||||
static func newTab(_ userdata: UnsafeMutableRawPointer?, config: ghostty_surface_config_s) {
|
|
||||||
let surface = self.surfaceUserdata(from: userdata)
|
|
||||||
|
|
||||||
guard let appState = self.appState(fromView: surface) else { return }
|
|
||||||
guard appState.config.windowDecorations else {
|
|
||||||
let alert = NSAlert()
|
|
||||||
alert.messageText = "Tabs are disabled"
|
|
||||||
alert.informativeText = "Enable window decorations to use tabs"
|
|
||||||
alert.addButton(withTitle: "OK")
|
|
||||||
alert.alertStyle = .warning
|
|
||||||
_ = alert.runModal()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
NotificationCenter.default.post(
|
|
||||||
name: Notification.ghosttyNewTab,
|
|
||||||
object: surface,
|
|
||||||
userInfo: [
|
|
||||||
Notification.NewSurfaceConfigKey: SurfaceConfiguration(from: config),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
static func newWindow(_ userdata: UnsafeMutableRawPointer?, config: ghostty_surface_config_s) {
|
|
||||||
let surface: SurfaceView? = if let userdata {
|
|
||||||
self.surfaceUserdata(from: userdata)
|
|
||||||
} else {
|
|
||||||
nil
|
|
||||||
}
|
|
||||||
|
|
||||||
NotificationCenter.default.post(
|
|
||||||
name: Notification.ghosttyNewWindow,
|
|
||||||
object: surface,
|
|
||||||
userInfo: [
|
|
||||||
Notification.NewSurfaceConfigKey: SurfaceConfiguration(from: config),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
static func controlInspector(_ userdata: UnsafeMutableRawPointer?, mode: ghostty_inspector_mode_e) {
|
|
||||||
let surface = self.surfaceUserdata(from: userdata)
|
|
||||||
NotificationCenter.default.post(name: Notification.didControlInspector, object: surface, userInfo: [
|
|
||||||
"mode": mode,
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
static func updateRendererHealth(_ userdata: UnsafeMutableRawPointer?, health: ghostty_renderer_health_e) {
|
|
||||||
let surface = self.surfaceUserdata(from: userdata)
|
|
||||||
NotificationCenter.default.post(
|
|
||||||
name: Notification.didUpdateRendererHealth,
|
|
||||||
object: surface,
|
|
||||||
userInfo: [
|
|
||||||
"health": health,
|
|
||||||
]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the GhosttyState from the given userdata value.
|
|
||||||
static private func appState(fromView view: SurfaceView) -> App? {
|
|
||||||
guard let surface = view.surface else { return nil }
|
|
||||||
guard let app = ghostty_surface_app(surface) else { return nil }
|
|
||||||
guard let app_ud = ghostty_app_userdata(app) else { return nil }
|
|
||||||
return Unmanaged<App>.fromOpaque(app_ud).takeUnretainedValue()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the surface view from the userdata.
|
|
||||||
static private func surfaceUserdata(from userdata: UnsafeMutableRawPointer?) -> SurfaceView {
|
|
||||||
return Unmanaged<SurfaceView>.fromOpaque(userdata!).takeUnretainedValue()
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -219,13 +219,13 @@ extension Ghostty {
|
|||||||
|
|
||||||
// Determine our desired direction
|
// Determine our desired direction
|
||||||
guard let directionAny = notification.userInfo?["direction"] else { return }
|
guard let directionAny = notification.userInfo?["direction"] else { return }
|
||||||
guard let direction = directionAny as? ghostty_split_direction_e else { return }
|
guard let direction = directionAny as? ghostty_action_split_direction_e else { return }
|
||||||
var splitDirection: SplitViewDirection
|
var splitDirection: SplitViewDirection
|
||||||
switch (direction) {
|
switch (direction) {
|
||||||
case GHOSTTY_SPLIT_RIGHT:
|
case GHOSTTY_SPLIT_DIRECTION_RIGHT:
|
||||||
splitDirection = .horizontal
|
splitDirection = .horizontal
|
||||||
|
|
||||||
case GHOSTTY_SPLIT_DOWN:
|
case GHOSTTY_SPLIT_DIRECTION_DOWN:
|
||||||
splitDirection = .vertical
|
splitDirection = .vertical
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -55,7 +55,7 @@ extension Ghostty {
|
|||||||
private func onControlInspector(_ notification: SwiftUI.Notification) {
|
private func onControlInspector(_ notification: SwiftUI.Notification) {
|
||||||
// Determine our mode
|
// Determine our mode
|
||||||
guard let modeAny = notification.userInfo?["mode"] else { return }
|
guard let modeAny = notification.userInfo?["mode"] else { return }
|
||||||
guard let mode = modeAny as? ghostty_inspector_mode_e else { return }
|
guard let mode = modeAny as? ghostty_action_inspector_e else { return }
|
||||||
|
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case GHOSTTY_INSPECTOR_TOGGLE:
|
case GHOSTTY_INSPECTOR_TOGGLE:
|
||||||
|
@ -39,32 +39,54 @@ extension Ghostty {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Surface Notifications
|
// MARK: Swift Types for C Types
|
||||||
|
|
||||||
extension Ghostty {
|
extension Ghostty {
|
||||||
|
enum SetSecureInput {
|
||||||
|
case on
|
||||||
|
case off
|
||||||
|
case toggle
|
||||||
|
|
||||||
|
static func from(_ c: ghostty_action_secure_input_e) -> Self? {
|
||||||
|
switch (c) {
|
||||||
|
case GHOSTTY_SECURE_INPUT_ON:
|
||||||
|
return .on
|
||||||
|
|
||||||
|
case GHOSTTY_SECURE_INPUT_OFF:
|
||||||
|
return .off
|
||||||
|
|
||||||
|
case GHOSTTY_SECURE_INPUT_TOGGLE:
|
||||||
|
return .toggle
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// An enum that is used for the directions that a split focus event can change.
|
/// An enum that is used for the directions that a split focus event can change.
|
||||||
enum SplitFocusDirection {
|
enum SplitFocusDirection {
|
||||||
case previous, next, top, bottom, left, right
|
case previous, next, top, bottom, left, right
|
||||||
|
|
||||||
/// Initialize from a Ghostty API enum.
|
/// Initialize from a Ghostty API enum.
|
||||||
static func from(direction: ghostty_split_focus_direction_e) -> Self? {
|
static func from(direction: ghostty_action_goto_split_e) -> Self? {
|
||||||
switch (direction) {
|
switch (direction) {
|
||||||
case GHOSTTY_SPLIT_FOCUS_PREVIOUS:
|
case GHOSTTY_GOTO_SPLIT_PREVIOUS:
|
||||||
return .previous
|
return .previous
|
||||||
|
|
||||||
case GHOSTTY_SPLIT_FOCUS_NEXT:
|
case GHOSTTY_GOTO_SPLIT_NEXT:
|
||||||
return .next
|
return .next
|
||||||
|
|
||||||
case GHOSTTY_SPLIT_FOCUS_TOP:
|
case GHOSTTY_GOTO_SPLIT_TOP:
|
||||||
return .top
|
return .top
|
||||||
|
|
||||||
case GHOSTTY_SPLIT_FOCUS_BOTTOM:
|
case GHOSTTY_GOTO_SPLIT_BOTTOM:
|
||||||
return .bottom
|
return .bottom
|
||||||
|
|
||||||
case GHOSTTY_SPLIT_FOCUS_LEFT:
|
case GHOSTTY_GOTO_SPLIT_LEFT:
|
||||||
return .left
|
return .left
|
||||||
|
|
||||||
case GHOSTTY_SPLIT_FOCUS_RIGHT:
|
case GHOSTTY_GOTO_SPLIT_RIGHT:
|
||||||
return .right
|
return .right
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@ -72,25 +94,25 @@ extension Ghostty {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func toNative() -> ghostty_split_focus_direction_e {
|
func toNative() -> ghostty_action_goto_split_e {
|
||||||
switch (self) {
|
switch (self) {
|
||||||
case .previous:
|
case .previous:
|
||||||
return GHOSTTY_SPLIT_FOCUS_PREVIOUS
|
return GHOSTTY_GOTO_SPLIT_PREVIOUS
|
||||||
|
|
||||||
case .next:
|
case .next:
|
||||||
return GHOSTTY_SPLIT_FOCUS_NEXT
|
return GHOSTTY_GOTO_SPLIT_NEXT
|
||||||
|
|
||||||
case .top:
|
case .top:
|
||||||
return GHOSTTY_SPLIT_FOCUS_TOP
|
return GHOSTTY_GOTO_SPLIT_TOP
|
||||||
|
|
||||||
case .bottom:
|
case .bottom:
|
||||||
return GHOSTTY_SPLIT_FOCUS_BOTTOM
|
return GHOSTTY_GOTO_SPLIT_BOTTOM
|
||||||
|
|
||||||
case .left:
|
case .left:
|
||||||
return GHOSTTY_SPLIT_FOCUS_LEFT
|
return GHOSTTY_GOTO_SPLIT_LEFT
|
||||||
|
|
||||||
case .right:
|
case .right:
|
||||||
return GHOSTTY_SPLIT_FOCUS_RIGHT
|
return GHOSTTY_GOTO_SPLIT_RIGHT
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -99,31 +121,31 @@ extension Ghostty {
|
|||||||
enum SplitResizeDirection {
|
enum SplitResizeDirection {
|
||||||
case up, down, left, right
|
case up, down, left, right
|
||||||
|
|
||||||
static func from(direction: ghostty_split_resize_direction_e) -> Self? {
|
static func from(direction: ghostty_action_resize_split_direction_e) -> Self? {
|
||||||
switch (direction) {
|
switch (direction) {
|
||||||
case GHOSTTY_SPLIT_RESIZE_UP:
|
case GHOSTTY_RESIZE_SPLIT_UP:
|
||||||
return .up;
|
return .up;
|
||||||
case GHOSTTY_SPLIT_RESIZE_DOWN:
|
case GHOSTTY_RESIZE_SPLIT_DOWN:
|
||||||
return .down;
|
return .down;
|
||||||
case GHOSTTY_SPLIT_RESIZE_LEFT:
|
case GHOSTTY_RESIZE_SPLIT_LEFT:
|
||||||
return .left;
|
return .left;
|
||||||
case GHOSTTY_SPLIT_RESIZE_RIGHT:
|
case GHOSTTY_RESIZE_SPLIT_RIGHT:
|
||||||
return .right;
|
return .right;
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func toNative() -> ghostty_split_resize_direction_e {
|
func toNative() -> ghostty_action_resize_split_direction_e {
|
||||||
switch (self) {
|
switch (self) {
|
||||||
case .up:
|
case .up:
|
||||||
return GHOSTTY_SPLIT_RESIZE_UP;
|
return GHOSTTY_RESIZE_SPLIT_UP;
|
||||||
case .down:
|
case .down:
|
||||||
return GHOSTTY_SPLIT_RESIZE_DOWN;
|
return GHOSTTY_RESIZE_SPLIT_DOWN;
|
||||||
case .left:
|
case .left:
|
||||||
return GHOSTTY_SPLIT_RESIZE_LEFT;
|
return GHOSTTY_RESIZE_SPLIT_LEFT;
|
||||||
case .right:
|
case .right:
|
||||||
return GHOSTTY_SPLIT_RESIZE_RIGHT;
|
return GHOSTTY_RESIZE_SPLIT_RIGHT;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -174,6 +196,8 @@ extension Ghostty {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: Surface Notifications
|
||||||
|
|
||||||
extension Ghostty.Notification {
|
extension Ghostty.Notification {
|
||||||
/// Used to pass a configuration along when creating a new tab/window/split.
|
/// Used to pass a configuration along when creating a new tab/window/split.
|
||||||
static let NewSurfaceConfigKey = "com.mitchellh.ghostty.newSurfaceConfig"
|
static let NewSurfaceConfigKey = "com.mitchellh.ghostty.newSurfaceConfig"
|
||||||
@ -201,7 +225,7 @@ extension Ghostty.Notification {
|
|||||||
|
|
||||||
/// Toggle fullscreen of current window
|
/// Toggle fullscreen of current window
|
||||||
static let ghosttyToggleFullscreen = Notification.Name("com.mitchellh.ghostty.toggleFullscreen")
|
static let ghosttyToggleFullscreen = Notification.Name("com.mitchellh.ghostty.toggleFullscreen")
|
||||||
static let NonNativeFullscreenKey = ghosttyToggleFullscreen.rawValue
|
static let FullscreenModeKey = ghosttyToggleFullscreen.rawValue
|
||||||
|
|
||||||
/// Notification that a surface is becoming focused. This is only sent on macOS 12 to
|
/// Notification that a surface is becoming focused. This is only sent on macOS 12 to
|
||||||
/// work around bugs. macOS 13+ should use the ".focused()" attribute.
|
/// work around bugs. macOS 13+ should use the ".focused()" attribute.
|
||||||
|
@ -251,7 +251,7 @@ extension Ghostty {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func setCursorShape(_ shape: ghostty_mouse_shape_e) {
|
func setCursorShape(_ shape: ghostty_action_mouse_shape_e) {
|
||||||
switch (shape) {
|
switch (shape) {
|
||||||
case GHOSTTY_MOUSE_SHAPE_DEFAULT:
|
case GHOSTTY_MOUSE_SHAPE_DEFAULT:
|
||||||
pointerStyle = .default
|
pointerStyle = .default
|
||||||
@ -312,7 +312,7 @@ extension Ghostty {
|
|||||||
|
|
||||||
@objc private func onUpdateRendererHealth(notification: SwiftUI.Notification) {
|
@objc private func onUpdateRendererHealth(notification: SwiftUI.Notification) {
|
||||||
guard let healthAny = notification.userInfo?["health"] else { return }
|
guard let healthAny = notification.userInfo?["health"] else { return }
|
||||||
guard let health = healthAny as? ghostty_renderer_health_e else { return }
|
guard let health = healthAny as? ghostty_action_renderer_health_e else { return }
|
||||||
healthy = health == GHOSTTY_RENDERER_HEALTH_OK
|
healthy = health == GHOSTTY_RENDERER_HEALTH_OK
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -926,12 +926,12 @@ extension Ghostty {
|
|||||||
|
|
||||||
@IBAction func splitRight(_ sender: Any) {
|
@IBAction func splitRight(_ sender: Any) {
|
||||||
guard let surface = self.surface else { return }
|
guard let surface = self.surface else { return }
|
||||||
ghostty_surface_split(surface, GHOSTTY_SPLIT_RIGHT)
|
ghostty_surface_split(surface, GHOSTTY_SPLIT_DIRECTION_RIGHT)
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func splitDown(_ sender: Any) {
|
@IBAction func splitDown(_ sender: Any) {
|
||||||
guard let surface = self.surface else { return }
|
guard let surface = self.surface else { return }
|
||||||
ghostty_surface_split(surface, GHOSTTY_SPLIT_DOWN)
|
ghostty_surface_split(surface, GHOSTTY_SPLIT_DIRECTION_DOWN)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func resetTerminal(_ sender: Any) {
|
@objc func resetTerminal(_ sender: Any) {
|
||||||
|
@ -13,8 +13,18 @@ class FullScreenHandler {
|
|||||||
var isInNonNativeFullscreen: Bool = false
|
var isInNonNativeFullscreen: Bool = false
|
||||||
var isInFullscreen: Bool = false
|
var isInFullscreen: Bool = false
|
||||||
|
|
||||||
func toggleFullscreen(window: NSWindow, nonNativeFullscreen: ghostty_non_native_fullscreen_e) {
|
func toggleFullscreen(window: NSWindow, mode: ghostty_action_fullscreen_e) {
|
||||||
let useNonNativeFullscreen = nonNativeFullscreen != GHOSTTY_NON_NATIVE_FULLSCREEN_FALSE
|
let useNonNativeFullscreen = switch (mode) {
|
||||||
|
case GHOSTTY_FULLSCREEN_NATIVE:
|
||||||
|
false
|
||||||
|
|
||||||
|
case GHOSTTY_FULLSCREEN_NON_NATIVE, GHOSTTY_FULLSCREEN_NON_NATIVE_VISIBLE_MENU:
|
||||||
|
true
|
||||||
|
|
||||||
|
default:
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
if isInFullscreen {
|
if isInFullscreen {
|
||||||
if useNonNativeFullscreen || isInNonNativeFullscreen {
|
if useNonNativeFullscreen || isInNonNativeFullscreen {
|
||||||
leaveFullscreen(window: window)
|
leaveFullscreen(window: window)
|
||||||
@ -27,7 +37,7 @@ class FullScreenHandler {
|
|||||||
isInFullscreen = false
|
isInFullscreen = false
|
||||||
} else {
|
} else {
|
||||||
if useNonNativeFullscreen {
|
if useNonNativeFullscreen {
|
||||||
let hideMenu = nonNativeFullscreen != GHOSTTY_NON_NATIVE_FULLSCREEN_VISIBLE_MENU
|
let hideMenu = mode != GHOSTTY_FULLSCREEN_NON_NATIVE_VISIBLE_MENU
|
||||||
enterFullscreen(window: window, hideMenu: hideMenu)
|
enterFullscreen(window: window, hideMenu: hideMenu)
|
||||||
isInNonNativeFullscreen = true
|
isInNonNativeFullscreen = true
|
||||||
} else {
|
} else {
|
||||||
|
134
src/Surface.zig
134
src/Surface.zig
@ -515,14 +515,25 @@ pub fn init(
|
|||||||
errdefer self.io.deinit();
|
errdefer self.io.deinit();
|
||||||
|
|
||||||
// Report initial cell size on surface creation
|
// Report initial cell size on surface creation
|
||||||
try rt_surface.setCellSize(cell_size.width, cell_size.height);
|
try rt_app.performAction(
|
||||||
|
.{ .surface = self },
|
||||||
|
.cell_size,
|
||||||
|
.{ .width = cell_size.width, .height = cell_size.height },
|
||||||
|
);
|
||||||
|
|
||||||
// Set a minimum size that is cols=10 h=4. This matches Mac's Terminal.app
|
// Set a minimum size that is cols=10 h=4. This matches Mac's Terminal.app
|
||||||
// but is otherwise somewhat arbitrary.
|
// but is otherwise somewhat arbitrary.
|
||||||
try rt_surface.setSizeLimits(.{
|
try rt_app.performAction(
|
||||||
.width = cell_size.width * 10,
|
.{ .surface = self },
|
||||||
.height = cell_size.height * 4,
|
.size_limit,
|
||||||
}, null);
|
.{
|
||||||
|
.min_width = cell_size.width * 10,
|
||||||
|
.min_height = cell_size.height * 4,
|
||||||
|
// No max:
|
||||||
|
.max_width = 0,
|
||||||
|
.max_height = 0,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
// Call our size callback which handles all our retina setup
|
// Call our size callback which handles all our retina setup
|
||||||
// Note: this shouldn't be necessary and when we clean up the surface
|
// Note: this shouldn't be necessary and when we clean up the surface
|
||||||
@ -576,13 +587,23 @@ pub fn init(
|
|||||||
padding.top +
|
padding.top +
|
||||||
padding.bottom;
|
padding.bottom;
|
||||||
|
|
||||||
rt_surface.setInitialWindowSize(final_width, final_height) catch |err| {
|
rt_app.performAction(
|
||||||
|
.{ .surface = self },
|
||||||
|
.initial_size,
|
||||||
|
.{ .width = final_width, .height = final_height },
|
||||||
|
) catch |err| {
|
||||||
|
// We don't treat this as a fatal error because not setting
|
||||||
|
// an initial size shouldn't stop our terminal from working.
|
||||||
log.warn("unable to set initial window size: {s}", .{err});
|
log.warn("unable to set initial window size: {s}", .{err});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.title) |title| {
|
if (config.title) |title| {
|
||||||
try rt_surface.setTitle(title);
|
try rt_app.performAction(
|
||||||
|
.{ .surface = self },
|
||||||
|
.set_title,
|
||||||
|
.{ .title = title },
|
||||||
|
);
|
||||||
} else if ((comptime builtin.os.tag == .linux) and
|
} else if ((comptime builtin.os.tag == .linux) and
|
||||||
config.@"_xdg-terminal-exec")
|
config.@"_xdg-terminal-exec")
|
||||||
xdg: {
|
xdg: {
|
||||||
@ -599,7 +620,11 @@ pub fn init(
|
|||||||
break :xdg;
|
break :xdg;
|
||||||
};
|
};
|
||||||
defer alloc.free(title);
|
defer alloc.free(title);
|
||||||
try rt_surface.setTitle(title);
|
try rt_app.performAction(
|
||||||
|
.{ .surface = self },
|
||||||
|
.set_title,
|
||||||
|
.{ .title = title },
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -743,7 +768,11 @@ pub fn handleMessage(self: *Surface, msg: Message) !void {
|
|||||||
// We know that our title should end in 0.
|
// We know that our title should end in 0.
|
||||||
const slice = std.mem.sliceTo(@as([*:0]const u8, @ptrCast(v)), 0);
|
const slice = std.mem.sliceTo(@as([*:0]const u8, @ptrCast(v)), 0);
|
||||||
log.debug("changing title \"{s}\"", .{slice});
|
log.debug("changing title \"{s}\"", .{slice});
|
||||||
try self.rt_surface.setTitle(slice);
|
try self.rt_app.performAction(
|
||||||
|
.{ .surface = self },
|
||||||
|
.set_title,
|
||||||
|
.{ .title = slice },
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
.report_title => |style| {
|
.report_title => |style| {
|
||||||
@ -769,7 +798,11 @@ pub fn handleMessage(self: *Surface, msg: Message) !void {
|
|||||||
|
|
||||||
.set_mouse_shape => |shape| {
|
.set_mouse_shape => |shape| {
|
||||||
log.debug("changing mouse shape: {}", .{shape});
|
log.debug("changing mouse shape: {}", .{shape});
|
||||||
try self.rt_surface.setMouseShape(shape);
|
try self.rt_app.performAction(
|
||||||
|
.{ .surface = self },
|
||||||
|
.mouse_shape,
|
||||||
|
shape,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
.clipboard_read => |clipboard| {
|
.clipboard_read => |clipboard| {
|
||||||
@ -897,7 +930,13 @@ fn modsChanged(self: *Surface, mods: input.Mods) void {
|
|||||||
/// Called when our renderer health state changes.
|
/// Called when our renderer health state changes.
|
||||||
fn updateRendererHealth(self: *Surface, health: renderer.Health) void {
|
fn updateRendererHealth(self: *Surface, health: renderer.Health) void {
|
||||||
log.warn("renderer health status change status={}", .{health});
|
log.warn("renderer health status change status={}", .{health});
|
||||||
self.rt_surface.updateRendererHealth(health);
|
self.rt_app.performAction(
|
||||||
|
.{ .surface = self },
|
||||||
|
.renderer_health,
|
||||||
|
health,
|
||||||
|
) catch |err| {
|
||||||
|
log.warn("failed to notify app of renderer health change err={}", .{err});
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update our configuration at runtime.
|
/// Update our configuration at runtime.
|
||||||
@ -1194,7 +1233,11 @@ fn setCellSize(self: *Surface, size: renderer.CellSize) !void {
|
|||||||
}, .unlocked);
|
}, .unlocked);
|
||||||
|
|
||||||
// Notify the window
|
// Notify the window
|
||||||
try self.rt_surface.setCellSize(size.width, size.height);
|
try self.rt_app.performAction(
|
||||||
|
.{ .surface = self },
|
||||||
|
.cell_size,
|
||||||
|
.{ .width = size.width, .height = size.height },
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Change the font size.
|
/// Change the font size.
|
||||||
@ -1214,10 +1257,14 @@ pub fn setFontSize(self: *Surface, size: font.face.DesiredSize) !void {
|
|||||||
errdefer self.app.font_grid_set.deref(font_grid_key);
|
errdefer self.app.font_grid_set.deref(font_grid_key);
|
||||||
|
|
||||||
// Set our cell size
|
// Set our cell size
|
||||||
try self.setCellSize(.{
|
try self.rt_app.performAction(
|
||||||
|
.{ .surface = self },
|
||||||
|
.cell_size,
|
||||||
|
.{
|
||||||
.width = font_grid.metrics.cell_width,
|
.width = font_grid.metrics.cell_width,
|
||||||
.height = font_grid.metrics.cell_height,
|
.height = font_grid.metrics.cell_height,
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
// Notify our render thread of the new font stack. The renderer
|
// Notify our render thread of the new font stack. The renderer
|
||||||
// MUST accept the new font grid and deref the old.
|
// MUST accept the new font grid and deref the old.
|
||||||
@ -1472,8 +1519,11 @@ pub fn keyCallback(
|
|||||||
.mods = self.mouse.mods,
|
.mods = self.mouse.mods,
|
||||||
.over_link = self.mouse.over_link,
|
.over_link = self.mouse.over_link,
|
||||||
.hidden = self.mouse.hidden,
|
.hidden = self.mouse.hidden,
|
||||||
}).keyToMouseShape()) |shape|
|
}).keyToMouseShape()) |shape| try self.rt_app.performAction(
|
||||||
try self.rt_surface.setMouseShape(shape);
|
.{ .surface = self },
|
||||||
|
.mouse_shape,
|
||||||
|
shape,
|
||||||
|
);
|
||||||
|
|
||||||
// We've processed a key event that produced some data so we want to
|
// We've processed a key event that produced some data so we want to
|
||||||
// track the last pressed key.
|
// track the last pressed key.
|
||||||
@ -2975,7 +3025,11 @@ pub fn cursorPosCallback(
|
|||||||
// We also queue a render so the renderer can undo the rendered link
|
// We also queue a render so the renderer can undo the rendered link
|
||||||
// state.
|
// state.
|
||||||
if (over_link) {
|
if (over_link) {
|
||||||
self.rt_surface.mouseOverLink(null);
|
try self.rt_app.performAction(
|
||||||
|
.{ .surface = self },
|
||||||
|
.mouse_over_link,
|
||||||
|
.{ .url = "" },
|
||||||
|
);
|
||||||
try self.queueRender();
|
try self.queueRender();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3061,7 +3115,11 @@ pub fn cursorPosCallback(
|
|||||||
self.renderer_state.mouse.point = pos_vp;
|
self.renderer_state.mouse.point = pos_vp;
|
||||||
self.mouse.over_link = true;
|
self.mouse.over_link = true;
|
||||||
self.renderer_state.terminal.screen.dirty.hyperlink_hover = true;
|
self.renderer_state.terminal.screen.dirty.hyperlink_hover = true;
|
||||||
try self.rt_surface.setMouseShape(.pointer);
|
try self.rt_app.performAction(
|
||||||
|
.{ .surface = self },
|
||||||
|
.mouse_shape,
|
||||||
|
.pointer,
|
||||||
|
);
|
||||||
|
|
||||||
switch (link[0]) {
|
switch (link[0]) {
|
||||||
.open => {
|
.open => {
|
||||||
@ -3070,7 +3128,11 @@ pub fn cursorPosCallback(
|
|||||||
.trim = false,
|
.trim = false,
|
||||||
});
|
});
|
||||||
defer self.alloc.free(str);
|
defer self.alloc.free(str);
|
||||||
self.rt_surface.mouseOverLink(str);
|
try self.rt_app.performAction(
|
||||||
|
.{ .surface = self },
|
||||||
|
.mouse_over_link,
|
||||||
|
.{ .url = str },
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
._open_osc8 => link: {
|
._open_osc8 => link: {
|
||||||
@ -3080,14 +3142,26 @@ pub fn cursorPosCallback(
|
|||||||
log.warn("failed to get URI for OSC8 hyperlink", .{});
|
log.warn("failed to get URI for OSC8 hyperlink", .{});
|
||||||
break :link;
|
break :link;
|
||||||
};
|
};
|
||||||
self.rt_surface.mouseOverLink(uri);
|
try self.rt_app.performAction(
|
||||||
|
.{ .surface = self },
|
||||||
|
.mouse_over_link,
|
||||||
|
.{ .url = uri },
|
||||||
|
);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
try self.queueRender();
|
try self.queueRender();
|
||||||
} else if (over_link) {
|
} else if (over_link) {
|
||||||
try self.rt_surface.setMouseShape(self.io.terminal.mouse_shape);
|
try self.rt_app.performAction(
|
||||||
self.rt_surface.mouseOverLink(null);
|
.{ .surface = self },
|
||||||
|
.mouse_shape,
|
||||||
|
self.io.terminal.mouse_shape,
|
||||||
|
);
|
||||||
|
try self.rt_app.performAction(
|
||||||
|
.{ .surface = self },
|
||||||
|
.mouse_over_link,
|
||||||
|
.{ .url = "" },
|
||||||
|
);
|
||||||
try self.queueRender();
|
try self.queueRender();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -3396,13 +3470,25 @@ fn scrollToBottom(self: *Surface) !void {
|
|||||||
fn hideMouse(self: *Surface) void {
|
fn hideMouse(self: *Surface) void {
|
||||||
if (self.mouse.hidden) return;
|
if (self.mouse.hidden) return;
|
||||||
self.mouse.hidden = true;
|
self.mouse.hidden = true;
|
||||||
self.rt_surface.setMouseVisibility(false);
|
self.rt_app.performAction(
|
||||||
|
.{ .surface = self },
|
||||||
|
.mouse_visibility,
|
||||||
|
.hidden,
|
||||||
|
) catch |err| {
|
||||||
|
log.warn("apprt failed to set mouse visibility err={}", .{err});
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn showMouse(self: *Surface) void {
|
fn showMouse(self: *Surface) void {
|
||||||
if (!self.mouse.hidden) return;
|
if (!self.mouse.hidden) return;
|
||||||
self.mouse.hidden = false;
|
self.mouse.hidden = false;
|
||||||
self.rt_surface.setMouseVisibility(true);
|
self.rt_app.performAction(
|
||||||
|
.{ .surface = self },
|
||||||
|
.mouse_visibility,
|
||||||
|
.visible,
|
||||||
|
) catch |err| {
|
||||||
|
log.warn("apprt failed to set mouse visibility err={}", .{err});
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Perform a binding action. A binding is a keybinding. This function
|
/// Perform a binding action. A binding is a keybinding. This function
|
||||||
|
@ -31,7 +31,6 @@ pub const ClipboardRequest = structs.ClipboardRequest;
|
|||||||
pub const ClipboardRequestType = structs.ClipboardRequestType;
|
pub const ClipboardRequestType = structs.ClipboardRequestType;
|
||||||
pub const ColorScheme = structs.ColorScheme;
|
pub const ColorScheme = structs.ColorScheme;
|
||||||
pub const CursorPos = structs.CursorPos;
|
pub const CursorPos = structs.CursorPos;
|
||||||
pub const DesktopNotification = structs.DesktopNotification;
|
|
||||||
pub const IMEPos = structs.IMEPos;
|
pub const IMEPos = structs.IMEPos;
|
||||||
pub const Selection = structs.Selection;
|
pub const Selection = structs.Selection;
|
||||||
pub const SurfaceSize = structs.SurfaceSize;
|
pub const SurfaceSize = structs.SurfaceSize;
|
||||||
|
@ -1,13 +1,45 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
|
const apprt = @import("../apprt.zig");
|
||||||
|
const renderer = @import("../renderer.zig");
|
||||||
|
const terminal = @import("../terminal/main.zig");
|
||||||
const CoreSurface = @import("../Surface.zig");
|
const CoreSurface = @import("../Surface.zig");
|
||||||
|
|
||||||
/// The target for an action. This is generally the thing that had focus
|
/// The target for an action. This is generally the thing that had focus
|
||||||
/// while the action was made but the concept of "focus" is not guaranteed
|
/// while the action was made but the concept of "focus" is not guaranteed
|
||||||
/// since actions can also be triggered by timers, scripts, etc.
|
/// since actions can also be triggered by timers, scripts, etc.
|
||||||
pub const Target = union(enum) {
|
pub const Target = union(Key) {
|
||||||
app,
|
app,
|
||||||
surface: *CoreSurface,
|
surface: *CoreSurface,
|
||||||
|
|
||||||
|
// Sync with: ghostty_target_tag_e
|
||||||
|
pub const Key = enum(c_int) {
|
||||||
|
app,
|
||||||
|
surface,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Sync with: ghostty_target_u
|
||||||
|
pub const CValue = extern union {
|
||||||
|
app: void,
|
||||||
|
surface: *apprt.Surface,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Sync with: ghostty_target_s
|
||||||
|
pub const C = extern struct {
|
||||||
|
key: Key,
|
||||||
|
value: CValue,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Convert to ghostty_target_s.
|
||||||
|
pub fn cval(self: Target) C {
|
||||||
|
return .{
|
||||||
|
.key = @as(Key, self),
|
||||||
|
.value = switch (self) {
|
||||||
|
.app => .{ .app = {} },
|
||||||
|
.surface => |v| .{ .surface = v.rt_surface },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The possible actions an apprt has to react to. Actions are one-way
|
/// The possible actions an apprt has to react to. Actions are one-way
|
||||||
@ -19,7 +51,23 @@ pub const Target = union(enum) {
|
|||||||
/// Importantly, actions are generally OPTIONAL to implement by an apprt.
|
/// Importantly, actions are generally OPTIONAL to implement by an apprt.
|
||||||
/// Required functionality is called directly on the runtime structure so
|
/// Required functionality is called directly on the runtime structure so
|
||||||
/// there is a compiler error if an action is not implemented.
|
/// there is a compiler error if an action is not implemented.
|
||||||
pub const Action = union(enum) {
|
pub const Action = union(Key) {
|
||||||
|
// A GUIDE TO ADDING NEW ACTIONS:
|
||||||
|
//
|
||||||
|
// 1. Add the action to the `Key` enum. The order of the enum matters
|
||||||
|
// because it maps directly to the libghostty C enum. For ABI
|
||||||
|
// compatibility, new actions should be added to the end of the enum.
|
||||||
|
//
|
||||||
|
// 2. Add the action and optional value to the Action union.
|
||||||
|
//
|
||||||
|
// 3. If the value type is not void, ensure the value is C ABI
|
||||||
|
// compatible (extern). If it is not, add a `C` decl to the value
|
||||||
|
// and a `cval` function to convert to the C ABI compatible value.
|
||||||
|
//
|
||||||
|
// 4. Update `include/ghostty.h`: add the new key, value, and union
|
||||||
|
// entry. If the value type is void then only the key needs to be
|
||||||
|
// added. Ensure the order matches exactly with the Zig code.
|
||||||
|
|
||||||
/// Open a new window. The target determines whether properties such
|
/// Open a new window. The target determines whether properties such
|
||||||
/// as font size should be inherited.
|
/// as font size should be inherited.
|
||||||
new_window,
|
new_window,
|
||||||
@ -62,12 +110,42 @@ pub const Action = union(enum) {
|
|||||||
/// Present the target terminal whether its a tab, split, or window.
|
/// Present the target terminal whether its a tab, split, or window.
|
||||||
present_terminal,
|
present_terminal,
|
||||||
|
|
||||||
|
/// Sets a size limit (in pixels) for the target terminal.
|
||||||
|
size_limit: SizeLimit,
|
||||||
|
|
||||||
|
/// Specifies the initial size 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_size: InitialSize,
|
||||||
|
|
||||||
|
/// The cell size has changed to the given dimensions in pixels.
|
||||||
|
cell_size: CellSize,
|
||||||
|
|
||||||
/// Control whether the inspector is shown or hidden.
|
/// Control whether the inspector is shown or hidden.
|
||||||
inspector: Inspector,
|
inspector: Inspector,
|
||||||
|
|
||||||
|
/// The inspector for the given target has changes and should be
|
||||||
|
/// rendered at the next opportunity.
|
||||||
|
render_inspector,
|
||||||
|
|
||||||
/// Show a desktop notification.
|
/// Show a desktop notification.
|
||||||
desktop_notification: DesktopNotification,
|
desktop_notification: DesktopNotification,
|
||||||
|
|
||||||
|
/// Set the title of the target.
|
||||||
|
set_title: SetTitle,
|
||||||
|
|
||||||
|
/// Set the mouse cursor shape.
|
||||||
|
mouse_shape: terminal.MouseShape,
|
||||||
|
|
||||||
|
/// Set whether the mouse cursor is visible or not.
|
||||||
|
mouse_visibility: MouseVisibility,
|
||||||
|
|
||||||
|
/// Called when the mouse is over or recently left a link.
|
||||||
|
mouse_over_link: MouseOverLink,
|
||||||
|
|
||||||
|
/// The health of the renderer has changed.
|
||||||
|
renderer_health: renderer.Health,
|
||||||
|
|
||||||
/// Open the Ghostty configuration. This is platform-specific about
|
/// Open the Ghostty configuration. This is platform-specific about
|
||||||
/// what it means; it can mean opening a dedicated UI or just opening
|
/// what it means; it can mean opening a dedicated UI or just opening
|
||||||
/// a file in a text editor.
|
/// a file in a text editor.
|
||||||
@ -86,8 +164,69 @@ pub const Action = union(enum) {
|
|||||||
/// system APIs to not log the input, etc.
|
/// system APIs to not log the input, etc.
|
||||||
secure_input: SecureInput,
|
secure_input: SecureInput,
|
||||||
|
|
||||||
/// The enum of keys in the tagged union.
|
/// Sync with: ghostty_action_tag_e
|
||||||
pub const Key = @typeInfo(Action).Union.tag_type.?;
|
pub const Key = enum(c_int) {
|
||||||
|
new_window,
|
||||||
|
new_tab,
|
||||||
|
new_split,
|
||||||
|
close_all_windows,
|
||||||
|
toggle_fullscreen,
|
||||||
|
toggle_window_decorations,
|
||||||
|
goto_tab,
|
||||||
|
goto_split,
|
||||||
|
resize_split,
|
||||||
|
equalize_splits,
|
||||||
|
toggle_split_zoom,
|
||||||
|
present_terminal,
|
||||||
|
size_limit,
|
||||||
|
initial_size,
|
||||||
|
cell_size,
|
||||||
|
inspector,
|
||||||
|
render_inspector,
|
||||||
|
desktop_notification,
|
||||||
|
set_title,
|
||||||
|
mouse_shape,
|
||||||
|
mouse_visibility,
|
||||||
|
mouse_over_link,
|
||||||
|
renderer_health,
|
||||||
|
open_config,
|
||||||
|
quit_timer,
|
||||||
|
secure_input,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Sync with: ghostty_action_u
|
||||||
|
pub const CValue = cvalue: {
|
||||||
|
const key_fields = @typeInfo(Key).Enum.fields;
|
||||||
|
var union_fields: [key_fields.len]std.builtin.Type.UnionField = undefined;
|
||||||
|
for (key_fields, 0..) |field, i| {
|
||||||
|
const action = @unionInit(Action, field.name, undefined);
|
||||||
|
const Type = t: {
|
||||||
|
const Type = @TypeOf(@field(action, field.name));
|
||||||
|
// Types can provide custom types for their CValue.
|
||||||
|
if (Type != void and @hasDecl(Type, "C")) break :t Type.C;
|
||||||
|
break :t Type;
|
||||||
|
};
|
||||||
|
|
||||||
|
union_fields[i] = .{
|
||||||
|
.name = field.name,
|
||||||
|
.type = Type,
|
||||||
|
.alignment = @alignOf(Type),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
break :cvalue @Type(.{ .Union = .{
|
||||||
|
.layout = .@"extern",
|
||||||
|
.tag_type = Key,
|
||||||
|
.fields = &union_fields,
|
||||||
|
.decls = &.{},
|
||||||
|
} });
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Sync with: ghostty_action_s
|
||||||
|
pub const C = extern struct {
|
||||||
|
key: Key,
|
||||||
|
value: CValue,
|
||||||
|
};
|
||||||
|
|
||||||
/// Returns the value type for the given key.
|
/// Returns the value type for the given key.
|
||||||
pub fn Value(comptime key: Key) type {
|
pub fn Value(comptime key: Key) type {
|
||||||
@ -98,6 +237,22 @@ pub const Action = union(enum) {
|
|||||||
|
|
||||||
unreachable;
|
unreachable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convert to ghostty_action_s.
|
||||||
|
pub fn cval(self: Action) C {
|
||||||
|
const value: CValue = switch (self) {
|
||||||
|
inline else => |v, tag| @unionInit(
|
||||||
|
CValue,
|
||||||
|
@tagName(tag),
|
||||||
|
if (@TypeOf(v) != void and @hasDecl(@TypeOf(v), "cval")) v.cval() else v,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.key = @as(Key, self),
|
||||||
|
.value = value,
|
||||||
|
};
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// This is made extern (c_int) to make interop easier with our embedded
|
// This is made extern (c_int) to make interop easier with our embedded
|
||||||
@ -120,7 +275,7 @@ pub const GotoSplit = enum(c_int) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/// The amount to resize the split by and the direction to resize it in.
|
/// The amount to resize the split by and the direction to resize it in.
|
||||||
pub const ResizeSplit = struct {
|
pub const ResizeSplit = extern struct {
|
||||||
amount: u16,
|
amount: u16,
|
||||||
direction: Direction,
|
direction: Direction,
|
||||||
|
|
||||||
@ -170,8 +325,75 @@ pub const QuitTimer = enum(c_int) {
|
|||||||
stop,
|
stop,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const MouseVisibility = enum(c_int) {
|
||||||
|
visible,
|
||||||
|
hidden,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const MouseOverLink = struct {
|
||||||
|
url: []const u8,
|
||||||
|
|
||||||
|
// Sync with: ghostty_action_mouse_over_link_s
|
||||||
|
pub const C = extern struct {
|
||||||
|
url: [*]const u8,
|
||||||
|
len: usize,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn cval(self: MouseOverLink) C {
|
||||||
|
return .{
|
||||||
|
.url = self.url.ptr,
|
||||||
|
.len = self.url.len,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const SizeLimit = extern struct {
|
||||||
|
min_width: u32,
|
||||||
|
min_height: u32,
|
||||||
|
max_width: u32,
|
||||||
|
max_height: u32,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const InitialSize = extern struct {
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const CellSize = extern struct {
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const SetTitle = struct {
|
||||||
|
title: [:0]const u8,
|
||||||
|
|
||||||
|
// Sync with: ghostty_action_set_title_s
|
||||||
|
pub const C = extern struct {
|
||||||
|
title: [*:0]const u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn cval(self: SetTitle) C {
|
||||||
|
return .{
|
||||||
|
.title = self.title.ptr,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/// The desktop notification to show.
|
/// The desktop notification to show.
|
||||||
pub const DesktopNotification = struct {
|
pub const DesktopNotification = struct {
|
||||||
title: [:0]const u8,
|
title: [:0]const u8,
|
||||||
body: [:0]const u8,
|
body: [:0]const u8,
|
||||||
|
|
||||||
|
// Sync with: ghostty_action_desktop_notification_s
|
||||||
|
pub const C = extern struct {
|
||||||
|
title: [*:0]const u8,
|
||||||
|
body: [*:0]const u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn cval(self: DesktopNotification) C {
|
||||||
|
return .{
|
||||||
|
.title = self.title.ptr,
|
||||||
|
.body = self.body.ptr,
|
||||||
|
};
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
@ -44,23 +44,14 @@ pub const App = struct {
|
|||||||
/// a full tick of the app loop.
|
/// a full tick of the app loop.
|
||||||
wakeup: *const fn (AppUD) callconv(.C) void,
|
wakeup: *const fn (AppUD) callconv(.C) void,
|
||||||
|
|
||||||
|
/// Callback called to handle an action.
|
||||||
|
action: *const fn (*App, apprt.Target.C, apprt.Action.C) callconv(.C) void,
|
||||||
|
|
||||||
/// Reload the configuration and return the new configuration.
|
/// Reload the configuration and return the new configuration.
|
||||||
/// The old configuration can be freed immediately when this is
|
/// The old configuration can be freed immediately when this is
|
||||||
/// called.
|
/// called.
|
||||||
reload_config: *const fn (AppUD) callconv(.C) ?*const Config,
|
reload_config: *const fn (AppUD) callconv(.C) ?*const Config,
|
||||||
|
|
||||||
/// Open the configuration file.
|
|
||||||
open_config: *const fn (AppUD) callconv(.C) void,
|
|
||||||
|
|
||||||
/// Called to set the title of the window.
|
|
||||||
set_title: *const fn (SurfaceUD, [*]const u8) callconv(.C) void,
|
|
||||||
|
|
||||||
/// Called to set the cursor shape.
|
|
||||||
set_mouse_shape: *const fn (SurfaceUD, terminal.MouseShape) callconv(.C) void,
|
|
||||||
|
|
||||||
/// Called to set the mouse visibility.
|
|
||||||
set_mouse_visibility: *const fn (SurfaceUD, bool) callconv(.C) void,
|
|
||||||
|
|
||||||
/// Read the clipboard value. The return value must be preserved
|
/// Read the clipboard value. The return value must be preserved
|
||||||
/// by the host until the next call. If there is no valid clipboard
|
/// by the host until the next call. If there is no valid clipboard
|
||||||
/// value then this should return null.
|
/// value then this should return null.
|
||||||
@ -79,73 +70,8 @@ pub const App = struct {
|
|||||||
/// Write the clipboard value.
|
/// Write the clipboard value.
|
||||||
write_clipboard: *const fn (SurfaceUD, [*:0]const u8, c_int, bool) callconv(.C) void,
|
write_clipboard: *const fn (SurfaceUD, [*:0]const u8, c_int, bool) callconv(.C) void,
|
||||||
|
|
||||||
/// Create a new split view. If the embedder doesn't support split
|
|
||||||
/// views then this can be null.
|
|
||||||
new_split: ?*const fn (SurfaceUD, apprt.action.SplitDirection, apprt.Surface.Options) callconv(.C) void = null,
|
|
||||||
|
|
||||||
/// New tab with options. The surface may be null if there is no target
|
|
||||||
/// surface in which case the apprt is expected to create a new window.
|
|
||||||
new_tab: ?*const fn (SurfaceUD, apprt.Surface.Options) callconv(.C) void = null,
|
|
||||||
|
|
||||||
/// New window with options. The surface may be null if there is no
|
|
||||||
/// target surface.
|
|
||||||
new_window: ?*const fn (SurfaceUD, apprt.Surface.Options) callconv(.C) void = null,
|
|
||||||
|
|
||||||
/// Control the inspector visibility
|
|
||||||
control_inspector: ?*const fn (SurfaceUD, apprt.action.Inspector) callconv(.C) void = null,
|
|
||||||
|
|
||||||
/// Close the current surface given by this function.
|
/// Close the current surface given by this function.
|
||||||
close_surface: ?*const fn (SurfaceUD, bool) callconv(.C) void = null,
|
close_surface: ?*const fn (SurfaceUD, bool) callconv(.C) void = null,
|
||||||
|
|
||||||
/// Focus the previous/next split (if any).
|
|
||||||
focus_split: ?*const fn (SurfaceUD, apprt.action.GotoSplit) callconv(.C) void = null,
|
|
||||||
|
|
||||||
/// Resize the current split.
|
|
||||||
resize_split: ?*const fn (SurfaceUD, apprt.action.ResizeSplit.Direction, u16) callconv(.C) void = null,
|
|
||||||
|
|
||||||
/// Equalize all splits in the current window
|
|
||||||
equalize_splits: ?*const fn (SurfaceUD) callconv(.C) void = null,
|
|
||||||
|
|
||||||
/// Zoom the current split.
|
|
||||||
toggle_split_zoom: ?*const fn (SurfaceUD) callconv(.C) void = null,
|
|
||||||
|
|
||||||
/// Goto tab
|
|
||||||
goto_tab: ?*const fn (SurfaceUD, apprt.action.GotoTab) callconv(.C) void = null,
|
|
||||||
|
|
||||||
/// Toggle fullscreen for current window.
|
|
||||||
toggle_fullscreen: ?*const fn (SurfaceUD, configpkg.NonNativeFullscreen) callconv(.C) void = null,
|
|
||||||
|
|
||||||
/// Set the initial window size. It is up to the user of libghostty to
|
|
||||||
/// determine if it is the initial window and set this appropriately.
|
|
||||||
set_initial_window_size: ?*const fn (SurfaceUD, u32, u32) callconv(.C) void = null,
|
|
||||||
|
|
||||||
/// Render the inspector for the given surface.
|
|
||||||
render_inspector: ?*const fn (SurfaceUD) callconv(.C) void = null,
|
|
||||||
|
|
||||||
/// Called when the cell size changes.
|
|
||||||
set_cell_size: ?*const fn (SurfaceUD, u32, u32) callconv(.C) void = null,
|
|
||||||
|
|
||||||
/// Show a desktop notification to the user. The surface may be null
|
|
||||||
/// if the notification is global.
|
|
||||||
show_desktop_notification: ?*const fn (SurfaceUD, [*:0]const u8, [*:0]const u8) void = null,
|
|
||||||
|
|
||||||
/// Called when the health of the renderer changes.
|
|
||||||
update_renderer_health: ?*const fn (SurfaceUD, renderer.Health) void = null,
|
|
||||||
|
|
||||||
/// Called when the mouse goes over a link. The link target is the
|
|
||||||
/// parameter. The link target will be null if the mouse is no longer
|
|
||||||
/// over a link.
|
|
||||||
mouse_over_link: ?*const fn (SurfaceUD, ?[*]const u8, usize) void = null,
|
|
||||||
|
|
||||||
/// Notifies that a password input has been started for the given
|
|
||||||
/// surface. The apprt can use this to modify UI, enable features
|
|
||||||
/// such as macOS secure input, etc.
|
|
||||||
///
|
|
||||||
/// The surface userdata will be null if a surface isn't focused.
|
|
||||||
set_password_input: ?*const fn (SurfaceUD, bool) callconv(.C) void = null,
|
|
||||||
|
|
||||||
/// Toggle secure input for the application.
|
|
||||||
toggle_secure_input: ?*const fn () callconv(.C) void = null,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// This is the key event sent for ghostty_surface_key and
|
/// This is the key event sent for ghostty_surface_key and
|
||||||
@ -492,213 +418,6 @@ pub const App = struct {
|
|||||||
surface.queueInspectorRender();
|
surface.queueInspectorRender();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn newWindow(self: *App, parent: ?*CoreSurface) !void {
|
|
||||||
// If we have a parent, the surface logic handles it.
|
|
||||||
if (parent) |surface| {
|
|
||||||
try surface.rt_surface.newWindow();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// No parent, call the new window callback.
|
|
||||||
const func = self.opts.new_window orelse {
|
|
||||||
log.info("runtime embedder does not support new_window", .{});
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
func(null, .{});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn toggleFullscreen(
|
|
||||||
self: *App,
|
|
||||||
target: apprt.Target,
|
|
||||||
fullscreen: apprt.action.Fullscreen,
|
|
||||||
) void {
|
|
||||||
const func = self.opts.toggle_fullscreen orelse {
|
|
||||||
log.info("runtime embedder does not toggle_fullscreen", .{});
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
switch (target) {
|
|
||||||
.app => {},
|
|
||||||
.surface => |v| func(
|
|
||||||
v.rt_surface.userdata,
|
|
||||||
switch (fullscreen) {
|
|
||||||
.native => .false,
|
|
||||||
.macos_non_native => .true,
|
|
||||||
.macos_non_native_visible_menu => .@"visible-menu",
|
|
||||||
},
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn newTab(self: *const App, target: apprt.Target) void {
|
|
||||||
const func = self.opts.new_tab orelse {
|
|
||||||
log.info("runtime embedder does not support new_tab", .{});
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
switch (target) {
|
|
||||||
.app => func(null, .{}),
|
|
||||||
.surface => |v| func(
|
|
||||||
v.rt_surface.userdata,
|
|
||||||
v.rt_surface.newSurfaceOptions(),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn gotoTab(self: *App, target: apprt.Target, tab: apprt.action.GotoTab) void {
|
|
||||||
const func = self.opts.goto_tab orelse {
|
|
||||||
log.info("runtime embedder does not support goto_tab", .{});
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
switch (target) {
|
|
||||||
.app => {},
|
|
||||||
.surface => |v| func(v.rt_surface.userdata, tab),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn newSplit(
|
|
||||||
self: *const App,
|
|
||||||
target: apprt.Target,
|
|
||||||
direction: apprt.action.SplitDirection,
|
|
||||||
) void {
|
|
||||||
const func = self.opts.new_split orelse {
|
|
||||||
log.info("runtime embedder does not support splits", .{});
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
switch (target) {
|
|
||||||
.app => func(null, direction, .{}),
|
|
||||||
.surface => |v| func(
|
|
||||||
v.rt_surface.userdata,
|
|
||||||
direction,
|
|
||||||
v.rt_surface.newSurfaceOptions(),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn gotoSplit(
|
|
||||||
self: *const App,
|
|
||||||
target: apprt.Target,
|
|
||||||
direction: apprt.action.GotoSplit,
|
|
||||||
) void {
|
|
||||||
const func = self.opts.focus_split orelse {
|
|
||||||
log.info("runtime embedder does not support focus split", .{});
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
switch (target) {
|
|
||||||
.app => {},
|
|
||||||
.surface => |v| func(v.rt_surface.userdata, direction),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn resizeSplit(
|
|
||||||
self: *const App,
|
|
||||||
target: apprt.Target,
|
|
||||||
resize: apprt.action.ResizeSplit,
|
|
||||||
) void {
|
|
||||||
const func = self.opts.resize_split orelse {
|
|
||||||
log.info("runtime embedder does not support resize split", .{});
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
switch (target) {
|
|
||||||
.app => {},
|
|
||||||
.surface => |v| func(
|
|
||||||
v.rt_surface.userdata,
|
|
||||||
resize.direction,
|
|
||||||
resize.amount,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn equalizeSplits(self: *const App, target: apprt.Target) void {
|
|
||||||
const func = self.opts.equalize_splits orelse {
|
|
||||||
log.info("runtime embedder does not support equalize splits", .{});
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
switch (target) {
|
|
||||||
.app => func(null),
|
|
||||||
.surface => |v| func(v.rt_surface.userdata),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn toggleSplitZoom(self: *const App, target: apprt.Target) void {
|
|
||||||
const func = self.opts.toggle_split_zoom orelse {
|
|
||||||
log.info("runtime embedder does not support split zoom", .{});
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
switch (target) {
|
|
||||||
.app => func(null),
|
|
||||||
.surface => |v| func(v.rt_surface.userdata),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn controlInspector(
|
|
||||||
self: *const App,
|
|
||||||
target: apprt.Target,
|
|
||||||
value: apprt.action.Inspector,
|
|
||||||
) void {
|
|
||||||
const func = self.opts.control_inspector orelse {
|
|
||||||
log.info("runtime embedder does not support the terminal inspector", .{});
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
switch (target) {
|
|
||||||
.app => {},
|
|
||||||
.surface => |v| func(v.rt_surface.userdata, value),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn showDesktopNotification(
|
|
||||||
self: *const App,
|
|
||||||
target: apprt.Target,
|
|
||||||
notification: apprt.action.DesktopNotification,
|
|
||||||
) void {
|
|
||||||
const func = self.opts.show_desktop_notification orelse {
|
|
||||||
log.info("runtime embedder does not support show_desktop_notification", .{});
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
func(switch (target) {
|
|
||||||
.app => null,
|
|
||||||
.surface => |v| v.rt_surface.userdata,
|
|
||||||
}, notification.title, notification.body);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn setPasswordInput(self: *App, target: apprt.Target, v: apprt.action.SecureInput) void {
|
|
||||||
switch (v) {
|
|
||||||
inline .on, .off => |tag| {
|
|
||||||
const func = self.opts.set_password_input orelse {
|
|
||||||
log.info("runtime embedder does not support set_password_input", .{});
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
func(switch (target) {
|
|
||||||
.app => null,
|
|
||||||
.surface => |surface| surface.rt_surface.userdata,
|
|
||||||
}, switch (tag) {
|
|
||||||
.on => true,
|
|
||||||
.off => false,
|
|
||||||
else => comptime unreachable,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
.toggle => {
|
|
||||||
const func = self.opts.toggle_secure_input orelse {
|
|
||||||
log.info("runtime embedder does not support toggle_secure_input", .{});
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
func();
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Perform a given action.
|
/// Perform a given action.
|
||||||
pub fn performAction(
|
pub fn performAction(
|
||||||
self: *App,
|
self: *App,
|
||||||
@ -706,32 +425,27 @@ pub const App = struct {
|
|||||||
comptime action: apprt.Action.Key,
|
comptime action: apprt.Action.Key,
|
||||||
value: apprt.Action.Value(action),
|
value: apprt.Action.Value(action),
|
||||||
) !void {
|
) !void {
|
||||||
|
// Special case certain actions before they are sent to the embedder
|
||||||
switch (action) {
|
switch (action) {
|
||||||
.new_window => _ = try self.newWindow(switch (target) {
|
.set_title => switch (target) {
|
||||||
.app => null,
|
.app => {},
|
||||||
.surface => |v| v,
|
.surface => |surface| {
|
||||||
}),
|
// Dupe the title so that we can store it. If we get an allocation
|
||||||
.toggle_fullscreen => self.toggleFullscreen(target, value),
|
// error we just ignore it, since this only breaks a few minor things.
|
||||||
|
const alloc = self.core_app.alloc;
|
||||||
|
if (surface.rt_surface.title) |v| alloc.free(v);
|
||||||
|
surface.rt_surface.title = alloc.dupeZ(u8, value.title) catch null;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
.new_tab => self.newTab(target),
|
else => {},
|
||||||
.goto_tab => self.gotoTab(target, value),
|
|
||||||
.new_split => self.newSplit(target, value),
|
|
||||||
.resize_split => self.resizeSplit(target, value),
|
|
||||||
.equalize_splits => self.equalizeSplits(target),
|
|
||||||
.toggle_split_zoom => self.toggleSplitZoom(target),
|
|
||||||
.goto_split => self.gotoSplit(target, value),
|
|
||||||
.open_config => try configpkg.edit.open(self.core_app.alloc),
|
|
||||||
.inspector => self.controlInspector(target, value),
|
|
||||||
.desktop_notification => self.showDesktopNotification(target, value),
|
|
||||||
.secure_input => self.setPasswordInput(target, value),
|
|
||||||
|
|
||||||
// Unimplemented
|
|
||||||
.present_terminal,
|
|
||||||
.close_all_windows,
|
|
||||||
.toggle_window_decorations,
|
|
||||||
.quit_timer,
|
|
||||||
=> log.warn("unimplemented action={}", .{action}),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.opts.action(
|
||||||
|
self,
|
||||||
|
target.cval(),
|
||||||
|
@unionInit(apprt.Action, @tagName(action), value).cval(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -966,44 +680,10 @@ pub const Surface = struct {
|
|||||||
return self.size;
|
return self.size;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setSizeLimits(self: *Surface, min: apprt.SurfaceSize, max_: ?apprt.SurfaceSize) !void {
|
|
||||||
_ = self;
|
|
||||||
_ = min;
|
|
||||||
_ = max_;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn setTitle(self: *Surface, slice: [:0]const u8) !void {
|
|
||||||
// Dupe the title so that we can store it. If we get an allocation
|
|
||||||
// error we just ignore it, since this only breaks a few minor things.
|
|
||||||
const alloc = self.app.core_app.alloc;
|
|
||||||
if (self.title) |v| alloc.free(v);
|
|
||||||
self.title = alloc.dupeZ(u8, slice) catch null;
|
|
||||||
|
|
||||||
self.app.opts.set_title(
|
|
||||||
self.userdata,
|
|
||||||
slice.ptr,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn getTitle(self: *Surface) ?[:0]const u8 {
|
pub fn getTitle(self: *Surface) ?[:0]const u8 {
|
||||||
return self.title;
|
return self.title;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setMouseShape(self: *Surface, shape: terminal.MouseShape) !void {
|
|
||||||
self.app.opts.set_mouse_shape(
|
|
||||||
self.userdata,
|
|
||||||
shape,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the visibility of the mouse cursor.
|
|
||||||
pub fn setMouseVisibility(self: *Surface, visible: bool) void {
|
|
||||||
self.app.opts.set_mouse_visibility(
|
|
||||||
self.userdata,
|
|
||||||
visible,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn supportsClipboard(
|
pub fn supportsClipboard(
|
||||||
self: *const Surface,
|
self: *const Surface,
|
||||||
clipboard_type: apprt.Clipboard,
|
clipboard_type: apprt.Clipboard,
|
||||||
@ -1236,44 +916,18 @@ pub const Surface = struct {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn newWindow(self: *const Surface) !void {
|
fn queueInspectorRender(self: *Surface) void {
|
||||||
const func = self.app.opts.new_window orelse {
|
self.app.performAction(
|
||||||
log.info("runtime embedder does not support new_window", .{});
|
.{ .surface = &self.core_surface },
|
||||||
|
.render_inspector,
|
||||||
|
{},
|
||||||
|
) catch |err| {
|
||||||
|
log.err("error rendering the inspector err={}", .{err});
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
const options = self.newSurfaceOptions();
|
|
||||||
func(self.userdata, options);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setInitialWindowSize(self: *const Surface, width: u32, height: u32) !void {
|
pub fn newSurfaceOptions(self: *const Surface) apprt.Surface.Options {
|
||||||
const func = self.app.opts.set_initial_window_size orelse {
|
|
||||||
log.info("runtime embedder does not set_initial_window_size", .{});
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
func(self.userdata, width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn queueInspectorRender(self: *const Surface) void {
|
|
||||||
const func = self.app.opts.render_inspector orelse {
|
|
||||||
log.info("runtime embedder does not render_inspector", .{});
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
func(self.userdata);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn setCellSize(self: *const Surface, width: u32, height: u32) !void {
|
|
||||||
const func = self.app.opts.set_cell_size orelse {
|
|
||||||
log.info("runtime embedder does not support set_cell_size", .{});
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
func(self.userdata, width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn newSurfaceOptions(self: *const Surface) apprt.Surface.Options {
|
|
||||||
const font_size: f32 = font_size: {
|
const font_size: f32 = font_size: {
|
||||||
if (!self.app.config.@"window-inherit-font-size") break :font_size 0;
|
if (!self.app.config.@"window-inherit-font-size") break :font_size 0;
|
||||||
break :font_size self.core_surface.font_size.points;
|
break :font_size self.core_surface.font_size.points;
|
||||||
@ -1290,29 +944,6 @@ pub const Surface = struct {
|
|||||||
const scale = try self.getContentScale();
|
const scale = try self.getContentScale();
|
||||||
return .{ .x = pos.x * scale.x, .y = pos.y * scale.y };
|
return .{ .x = pos.x * scale.x, .y = pos.y * scale.y };
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update the health of the renderer.
|
|
||||||
pub fn updateRendererHealth(self: *const Surface, health: renderer.Health) void {
|
|
||||||
const func = self.app.opts.update_renderer_health orelse {
|
|
||||||
log.info("runtime embedder does not support update_renderer_health", .{});
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
func(self.userdata, health);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn mouseOverLink(self: *const Surface, uri: ?[]const u8) void {
|
|
||||||
const func = self.app.opts.mouse_over_link orelse {
|
|
||||||
log.info("runtime embedder does not support over_link", .{});
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (uri) |v| {
|
|
||||||
func(self.userdata, v.ptr, v.len);
|
|
||||||
} else {
|
|
||||||
func(self.userdata, null, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Inspector is the state required for the terminal inspector. A terminal
|
/// Inspector is the state required for the terminal inspector. A terminal
|
||||||
@ -1735,11 +1366,21 @@ pub const CAPI = struct {
|
|||||||
ptr.app.closeSurface(ptr);
|
ptr.app.closeSurface(ptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the userdata associated with the surface.
|
||||||
|
export fn ghostty_surface_userdata(surface: *Surface) ?*anyopaque {
|
||||||
|
return surface.userdata;
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the app associated with a surface.
|
/// Returns the app associated with a surface.
|
||||||
export fn ghostty_surface_app(surface: *Surface) *App {
|
export fn ghostty_surface_app(surface: *Surface) *App {
|
||||||
return surface.app;
|
return surface.app;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the config to use for surfaces that inherit from this one.
|
||||||
|
export fn ghostty_surface_inherited_config(surface: *Surface) Surface.Options {
|
||||||
|
return surface.newSurfaceOptions();
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns true if the surface needs to confirm quitting.
|
/// Returns true if the surface needs to confirm quitting.
|
||||||
export fn ghostty_surface_needs_confirm_quit(surface: *Surface) bool {
|
export fn ghostty_surface_needs_confirm_quit(surface: *Surface) bool {
|
||||||
return surface.core_surface.needsConfirmQuit();
|
return surface.core_surface.needsConfirmQuit();
|
||||||
|
@ -134,8 +134,6 @@ pub const App = struct {
|
|||||||
comptime action: apprt.Action.Key,
|
comptime action: apprt.Action.Key,
|
||||||
value: apprt.Action.Value(action),
|
value: apprt.Action.Value(action),
|
||||||
) !void {
|
) !void {
|
||||||
_ = value;
|
|
||||||
|
|
||||||
switch (action) {
|
switch (action) {
|
||||||
.new_window => _ = try self.newSurface(switch (target) {
|
.new_window => _ = try self.newSurface(switch (target) {
|
||||||
.app => null,
|
.app => null,
|
||||||
@ -147,10 +145,47 @@ pub const App = struct {
|
|||||||
.surface => |v| v,
|
.surface => |v| v,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
.size_limit => switch (target) {
|
||||||
|
.app => {},
|
||||||
|
.surface => |surface| try surface.rt_surface.setSizeLimits(.{
|
||||||
|
.width = value.min_width,
|
||||||
|
.height = value.min_height,
|
||||||
|
}, if (value.max_width > 0) .{
|
||||||
|
.width = value.max_width,
|
||||||
|
.height = value.max_height,
|
||||||
|
} else null),
|
||||||
|
},
|
||||||
|
|
||||||
|
.initial_size => switch (target) {
|
||||||
|
.app => {},
|
||||||
|
.surface => |surface| try surface.rt_surface.setInitialWindowSize(
|
||||||
|
value.width,
|
||||||
|
value.height,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
.toggle_fullscreen => self.toggleFullscreen(target),
|
.toggle_fullscreen => self.toggleFullscreen(target),
|
||||||
|
|
||||||
.open_config => try configpkg.edit.open(self.app.alloc),
|
.open_config => try configpkg.edit.open(self.app.alloc),
|
||||||
|
|
||||||
|
.set_title => switch (target) {
|
||||||
|
.app => {},
|
||||||
|
.surface => |surface| try surface.rt_surface.setTitle(value.title),
|
||||||
|
},
|
||||||
|
|
||||||
|
.mouse_shape => switch (target) {
|
||||||
|
.app => {},
|
||||||
|
.surface => |surface| try surface.rt_surface.setMouseShape(value),
|
||||||
|
},
|
||||||
|
|
||||||
|
.mouse_visibility => switch (target) {
|
||||||
|
.app => {},
|
||||||
|
.surface => |surface| surface.rt_surface.setMouseVisibility(switch (value) {
|
||||||
|
.visible => true,
|
||||||
|
.hidden => false,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
|
||||||
// Unimplemented
|
// Unimplemented
|
||||||
.new_split,
|
.new_split,
|
||||||
.goto_split,
|
.goto_split,
|
||||||
@ -162,9 +197,13 @@ pub const App = struct {
|
|||||||
.toggle_window_decorations,
|
.toggle_window_decorations,
|
||||||
.goto_tab,
|
.goto_tab,
|
||||||
.inspector,
|
.inspector,
|
||||||
|
.render_inspector,
|
||||||
.quit_timer,
|
.quit_timer,
|
||||||
.secure_input,
|
.secure_input,
|
||||||
.desktop_notification,
|
.desktop_notification,
|
||||||
|
.mouse_over_link,
|
||||||
|
.cell_size,
|
||||||
|
.renderer_health,
|
||||||
=> log.info("unimplemented action={}", .{action}),
|
=> log.info("unimplemented action={}", .{action}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -581,7 +620,7 @@ pub const Surface = struct {
|
|||||||
/// Set the initial window size. This is called exactly once at
|
/// Set the initial window size. This is called exactly once at
|
||||||
/// surface initialization time. This may be called before "self"
|
/// surface initialization time. This may be called before "self"
|
||||||
/// is fully initialized.
|
/// is fully initialized.
|
||||||
pub fn setInitialWindowSize(self: *const Surface, width: u32, height: u32) !void {
|
fn setInitialWindowSize(self: *const Surface, width: u32, height: u32) !void {
|
||||||
const monitor = self.window.getMonitor() orelse glfw.Monitor.getPrimary() orelse {
|
const monitor = self.window.getMonitor() orelse glfw.Monitor.getPrimary() orelse {
|
||||||
log.warn("window is not on a monitor, not setting initial size", .{});
|
log.warn("window is not on a monitor, not setting initial size", .{});
|
||||||
return;
|
return;
|
||||||
@ -594,18 +633,11 @@ pub const Surface = struct {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the cell size. Unused by GLFW.
|
|
||||||
pub fn setCellSize(self: *const Surface, width: u32, height: u32) !void {
|
|
||||||
_ = self;
|
|
||||||
_ = width;
|
|
||||||
_ = height;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the size limits of the window.
|
/// Set the size limits of the window.
|
||||||
/// Note: this interface is not good, we should redo it if we plan
|
/// 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,
|
/// to use this more. i.e. you can't set max width but no max height,
|
||||||
/// or no mins.
|
/// or no mins.
|
||||||
pub fn setSizeLimits(self: *Surface, min: apprt.SurfaceSize, max_: ?apprt.SurfaceSize) !void {
|
fn setSizeLimits(self: *Surface, min: apprt.SurfaceSize, max_: ?apprt.SurfaceSize) !void {
|
||||||
self.window.setSizeLimits(.{
|
self.window.setSizeLimits(.{
|
||||||
.width = min.width,
|
.width = min.width,
|
||||||
.height = min.height,
|
.height = min.height,
|
||||||
@ -655,7 +687,7 @@ pub const Surface = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Set the title of the window.
|
/// Set the title of the window.
|
||||||
pub fn setTitle(self: *Surface, slice: [:0]const u8) !void {
|
fn setTitle(self: *Surface, slice: [:0]const u8) !void {
|
||||||
if (self.title_text) |t| self.core_surface.alloc.free(t);
|
if (self.title_text) |t| self.core_surface.alloc.free(t);
|
||||||
self.title_text = try self.core_surface.alloc.dupeZ(u8, slice);
|
self.title_text = try self.core_surface.alloc.dupeZ(u8, slice);
|
||||||
self.window.setTitle(self.title_text.?.ptr);
|
self.window.setTitle(self.title_text.?.ptr);
|
||||||
@ -667,7 +699,7 @@ pub const Surface = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Set the shape of the cursor.
|
/// Set the shape of the cursor.
|
||||||
pub fn setMouseShape(self: *Surface, shape: terminal.MouseShape) !void {
|
fn setMouseShape(self: *Surface, shape: terminal.MouseShape) !void {
|
||||||
if ((comptime builtin.target.isDarwin()) and
|
if ((comptime builtin.target.isDarwin()) and
|
||||||
!internal_os.macosVersionAtLeast(13, 0, 0))
|
!internal_os.macosVersionAtLeast(13, 0, 0))
|
||||||
{
|
{
|
||||||
@ -703,23 +735,11 @@ pub const Surface = struct {
|
|||||||
self.cursor = new;
|
self.cursor = new;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn mouseOverLink(self: *Surface, uri: ?[]const u8) void {
|
|
||||||
// We don't do anything in GLFW.
|
|
||||||
_ = self;
|
|
||||||
_ = uri;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the visibility of the mouse cursor.
|
/// Set the visibility of the mouse cursor.
|
||||||
pub fn setMouseVisibility(self: *Surface, visible: bool) void {
|
fn setMouseVisibility(self: *Surface, visible: bool) void {
|
||||||
self.window.setInputModeCursor(if (visible) .normal else .hidden);
|
self.window.setInputModeCursor(if (visible) .normal else .hidden);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn updateRendererHealth(self: *const Surface, health: renderer.Health) void {
|
|
||||||
// We don't support this in GLFW.
|
|
||||||
_ = self;
|
|
||||||
_ = health;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn supportsClipboard(
|
pub fn supportsClipboard(
|
||||||
self: *const Surface,
|
self: *const Surface,
|
||||||
clipboard_type: apprt.Clipboard,
|
clipboard_type: apprt.Clipboard,
|
||||||
|
@ -17,6 +17,7 @@ const apprt = @import("../../apprt.zig");
|
|||||||
const configpkg = @import("../../config.zig");
|
const configpkg = @import("../../config.zig");
|
||||||
const input = @import("../../input.zig");
|
const input = @import("../../input.zig");
|
||||||
const internal_os = @import("../../os/main.zig");
|
const internal_os = @import("../../os/main.zig");
|
||||||
|
const terminal = @import("../../terminal/main.zig");
|
||||||
const Config = configpkg.Config;
|
const Config = configpkg.Config;
|
||||||
const CoreApp = @import("../../App.zig");
|
const CoreApp = @import("../../App.zig");
|
||||||
const CoreSurface = @import("../../Surface.zig");
|
const CoreSurface = @import("../../Surface.zig");
|
||||||
@ -365,14 +366,23 @@ pub fn performAction(
|
|||||||
.open_config => try configpkg.edit.open(self.core_app.alloc),
|
.open_config => try configpkg.edit.open(self.core_app.alloc),
|
||||||
.inspector => self.controlInspector(target, value),
|
.inspector => self.controlInspector(target, value),
|
||||||
.desktop_notification => self.showDesktopNotification(target, value),
|
.desktop_notification => self.showDesktopNotification(target, value),
|
||||||
|
.set_title => try self.setTitle(target, value),
|
||||||
.present_terminal => self.presentTerminal(target),
|
.present_terminal => self.presentTerminal(target),
|
||||||
|
.initial_size => try self.setInitialSize(target, value),
|
||||||
|
.mouse_visibility => self.setMouseVisibility(target, value),
|
||||||
|
.mouse_shape => try self.setMouseShape(target, value),
|
||||||
|
.mouse_over_link => self.setMouseOverLink(target, value),
|
||||||
.toggle_window_decorations => self.toggleWindowDecorations(target),
|
.toggle_window_decorations => self.toggleWindowDecorations(target),
|
||||||
.quit_timer => self.quitTimer(value),
|
.quit_timer => self.quitTimer(value),
|
||||||
|
|
||||||
// Unimplemented
|
// Unimplemented
|
||||||
.close_all_windows,
|
.close_all_windows,
|
||||||
.toggle_split_zoom,
|
.toggle_split_zoom,
|
||||||
|
.size_limit,
|
||||||
|
.cell_size,
|
||||||
.secure_input,
|
.secure_input,
|
||||||
|
.render_inspector,
|
||||||
|
.renderer_health,
|
||||||
=> log.warn("unimplemented action={}", .{action}),
|
=> log.warn("unimplemented action={}", .{action}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -551,6 +561,66 @@ fn quitTimer(self: *App, mode: apprt.action.QuitTimer) void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn setTitle(
|
||||||
|
_: *App,
|
||||||
|
target: apprt.Target,
|
||||||
|
title: apprt.action.SetTitle,
|
||||||
|
) !void {
|
||||||
|
switch (target) {
|
||||||
|
.app => {},
|
||||||
|
.surface => |v| try v.rt_surface.setTitle(title.title),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setMouseVisibility(
|
||||||
|
_: *App,
|
||||||
|
target: apprt.Target,
|
||||||
|
visibility: apprt.action.MouseVisibility,
|
||||||
|
) void {
|
||||||
|
switch (target) {
|
||||||
|
.app => {},
|
||||||
|
.surface => |v| v.rt_surface.setMouseVisibility(switch (visibility) {
|
||||||
|
.visible => true,
|
||||||
|
.hidden => false,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setMouseShape(
|
||||||
|
_: *App,
|
||||||
|
target: apprt.Target,
|
||||||
|
shape: terminal.MouseShape,
|
||||||
|
) !void {
|
||||||
|
switch (target) {
|
||||||
|
.app => {},
|
||||||
|
.surface => |v| try v.rt_surface.setMouseShape(shape),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setMouseOverLink(
|
||||||
|
_: *App,
|
||||||
|
target: apprt.Target,
|
||||||
|
value: apprt.action.MouseOverLink,
|
||||||
|
) void {
|
||||||
|
switch (target) {
|
||||||
|
.app => {},
|
||||||
|
.surface => |v| v.rt_surface.mouseOverLink(value.url),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setInitialSize(
|
||||||
|
_: *App,
|
||||||
|
target: apprt.Target,
|
||||||
|
value: apprt.action.InitialSize,
|
||||||
|
) !void {
|
||||||
|
switch (target) {
|
||||||
|
.app => {},
|
||||||
|
.surface => |v| try v.rt_surface.setInitialWindowSize(
|
||||||
|
value.width,
|
||||||
|
value.height,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
fn showDesktopNotification(
|
fn showDesktopNotification(
|
||||||
self: *App,
|
self: *App,
|
||||||
target: apprt.Target,
|
target: apprt.Target,
|
||||||
|
@ -784,18 +784,6 @@ pub fn setInitialWindowSize(self: *const Surface, width: u32, height: u32) !void
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setCellSize(self: *const Surface, width: u32, height: u32) !void {
|
|
||||||
_ = self;
|
|
||||||
_ = width;
|
|
||||||
_ = height;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn setSizeLimits(self: *Surface, min: apprt.SurfaceSize, max_: ?apprt.SurfaceSize) !void {
|
|
||||||
_ = self;
|
|
||||||
_ = min;
|
|
||||||
_ = max_;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn grabFocus(self: *Surface) void {
|
pub fn grabFocus(self: *Surface) void {
|
||||||
if (self.container.tab()) |tab| tab.focus_child = self;
|
if (self.container.tab()) |tab| tab.focus_child = self;
|
||||||
|
|
||||||
@ -1921,9 +1909,3 @@ pub fn present(self: *Surface) void {
|
|||||||
|
|
||||||
self.grabFocus();
|
self.grabFocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn updateRendererHealth(self: *const Surface, health: renderer.Health) void {
|
|
||||||
// We don't support this in GTK.
|
|
||||||
_ = self;
|
|
||||||
_ = health;
|
|
||||||
}
|
|
||||||
|
@ -52,16 +52,6 @@ pub const ClipboardRequest = union(ClipboardRequestType) {
|
|||||||
osc_52_write: Clipboard,
|
osc_52_write: Clipboard,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A desktop notification.
|
|
||||||
pub const DesktopNotification = struct {
|
|
||||||
/// The title of the notification. May be an empty string to not show a
|
|
||||||
/// title.
|
|
||||||
title: []const u8,
|
|
||||||
|
|
||||||
/// The body of a notification. This will always be shown.
|
|
||||||
body: []const u8,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// The color scheme in use (light vs dark).
|
/// The color scheme in use (light vs dark).
|
||||||
pub const ColorScheme = enum(u2) {
|
pub const ColorScheme = enum(u2) {
|
||||||
light = 0,
|
light = 0,
|
||||||
|
Reference in New Issue
Block a user