mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-08-02 14:57:31 +03:00
macos: show URL on OSC8 hover
This commit is contained in:
@ -452,6 +452,7 @@ typedef void (*ghostty_runtime_show_desktop_notification_cb)(void*,
|
|||||||
const char*);
|
const char*);
|
||||||
typedef void (
|
typedef void (
|
||||||
*ghostty_runtime_update_renderer_health)(void*, ghostty_renderer_health_e);
|
*ghostty_runtime_update_renderer_health)(void*, ghostty_renderer_health_e);
|
||||||
|
typedef void (*ghostty_runtime_mouse_over_link_cb)(void*, const char*, size_t);
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
void* userdata;
|
void* userdata;
|
||||||
@ -481,6 +482,7 @@ typedef struct {
|
|||||||
ghostty_runtime_set_cell_size_cb set_cell_size_cb;
|
ghostty_runtime_set_cell_size_cb set_cell_size_cb;
|
||||||
ghostty_runtime_show_desktop_notification_cb show_desktop_notification_cb;
|
ghostty_runtime_show_desktop_notification_cb show_desktop_notification_cb;
|
||||||
ghostty_runtime_update_renderer_health update_renderer_health_cb;
|
ghostty_runtime_update_renderer_health update_renderer_health_cb;
|
||||||
|
ghostty_runtime_mouse_over_link_cb mouse_over_link_cb;
|
||||||
} ghostty_runtime_config_s;
|
} ghostty_runtime_config_s;
|
||||||
|
|
||||||
//-------------------------------------------------------------------
|
//-------------------------------------------------------------------
|
||||||
|
@ -93,7 +93,8 @@ extension Ghostty {
|
|||||||
set_cell_size_cb: { userdata, width, height in App.setCellSize(userdata, width: width, height: height) },
|
set_cell_size_cb: { userdata, width, height in App.setCellSize(userdata, width: width, height: height) },
|
||||||
show_desktop_notification_cb: { userdata, title, body in
|
show_desktop_notification_cb: { userdata, title, body in
|
||||||
App.showUserNotification(userdata, title: title, body: body) },
|
App.showUserNotification(userdata, title: title, body: body) },
|
||||||
update_renderer_health_cb: { userdata, health in App.updateRendererHealth(userdata, health: health) }
|
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) }
|
||||||
)
|
)
|
||||||
|
|
||||||
// Create the ghostty app.
|
// Create the ghostty app.
|
||||||
@ -523,6 +524,17 @@ extension Ghostty {
|
|||||||
let backingSize = NSSize(width: Double(width), height: Double(height))
|
let backingSize = NSSize(width: Double(width), height: Double(height))
|
||||||
surfaceView.cellSize = surfaceView.convertFromBacking(backingSize)
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
let buffer = Data(bytes: uri!, count: len)
|
||||||
|
surfaceView.hoverUrl = String(data: buffer, encoding: .utf8)
|
||||||
|
}
|
||||||
|
|
||||||
static func showUserNotification(_ userdata: UnsafeMutableRawPointer?, title: UnsafePointer<CChar>?, body: UnsafePointer<CChar>?) {
|
static func showUserNotification(_ userdata: UnsafeMutableRawPointer?, title: UnsafePointer<CChar>?, body: UnsafePointer<CChar>?) {
|
||||||
let surfaceView = self.surfaceUserdata(from: userdata)
|
let surfaceView = self.surfaceUserdata(from: userdata)
|
||||||
|
@ -147,13 +147,13 @@ extension Ghostty {
|
|||||||
|
|
||||||
// If we have a URL from hovering a link, we show that.
|
// If we have a URL from hovering a link, we show that.
|
||||||
// TODO
|
// TODO
|
||||||
if (false) {
|
if let url = surfaceView.hoverUrl {
|
||||||
let padding: CGFloat = 3
|
let padding: CGFloat = 3
|
||||||
HStack {
|
HStack {
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
Text(verbatim: "http://example.com")
|
Text(verbatim: url)
|
||||||
.padding(.init(top: padding, leading: padding, bottom: padding, trailing: padding))
|
.padding(.init(top: padding, leading: padding, bottom: padding, trailing: padding))
|
||||||
.background(.background)
|
.background(.background)
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,9 @@ extension Ghostty {
|
|||||||
|
|
||||||
// Any error while initializing the surface.
|
// Any error while initializing the surface.
|
||||||
@Published var error: Error? = nil
|
@Published var error: Error? = nil
|
||||||
|
|
||||||
|
// The hovered URL string
|
||||||
|
@Published var hoverUrl: String? = nil
|
||||||
|
|
||||||
// An initial size to request for a window. This will only affect
|
// An initial size to request for a window. This will only affect
|
||||||
// then the view is moved to a new window.
|
// then the view is moved to a new window.
|
||||||
|
@ -2813,14 +2813,34 @@ pub fn cursorPosCallback(
|
|||||||
}
|
}
|
||||||
self.mouse.link_point = pos_vp;
|
self.mouse.link_point = pos_vp;
|
||||||
|
|
||||||
if (try self.linkAtPos(pos)) |_| {
|
if (try self.linkAtPos(pos)) |link| {
|
||||||
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_surface.setMouseShape(.pointer);
|
||||||
|
|
||||||
|
switch (link[0]) {
|
||||||
|
.open => {},
|
||||||
|
|
||||||
|
._open_osc8 => link: {
|
||||||
|
// Show the URL in the status bar
|
||||||
|
const pin = link[1].start();
|
||||||
|
const page = &pin.page.data;
|
||||||
|
const cell = pin.rowAndCell().cell;
|
||||||
|
const link_id = page.lookupHyperlink(cell) orelse {
|
||||||
|
log.warn("failed to find hyperlink for cell", .{});
|
||||||
|
break :link;
|
||||||
|
};
|
||||||
|
const entry = page.hyperlink_set.get(page.memory, link_id);
|
||||||
|
const uri = entry.uri.offset.ptr(page.memory)[0..entry.uri.len];
|
||||||
|
self.rt_surface.mouseOverLink(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_surface.setMouseShape(self.io.terminal.mouse_shape);
|
||||||
|
self.rt_surface.mouseOverLink(null);
|
||||||
try self.queueRender();
|
try self.queueRender();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -128,6 +128,11 @@ pub const App = struct {
|
|||||||
|
|
||||||
/// Called when the health of the renderer changes.
|
/// Called when the health of the renderer changes.
|
||||||
update_renderer_health: ?*const fn (SurfaceUD, renderer.Health) void = null,
|
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,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Special values for the goto_tab callback.
|
/// Special values for the goto_tab callback.
|
||||||
@ -1101,6 +1106,19 @@ pub const Surface = struct {
|
|||||||
|
|
||||||
func(self.userdata, health);
|
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
|
||||||
|
Reference in New Issue
Block a user