diff options
Diffstat (limited to 'src/platform')
| -rw-r--r-- | src/platform/cocoa/window.m | 275 | ||||
| -rw-r--r-- | src/platform/wayland/api.h | 3 | ||||
| -rw-r--r-- | src/platform/wayland/backend.c | 78 | ||||
| -rw-r--r-- | src/platform/wayland/buffer.c | 15 | ||||
| -rw-r--r-- | src/platform/wayland/clipboard.c | 152 | ||||
| -rw-r--r-- | src/platform/wayland/clipboard.h | 9 | ||||
| -rw-r--r-- | src/platform/wayland/drag.c | 8 | ||||
| -rw-r--r-- | src/platform/wayland/ime.c | 5 | ||||
| -rw-r--r-- | src/platform/wayland/keyboard.c | 54 | ||||
| -rw-r--r-- | src/platform/wayland/pointer.c | 10 | ||||
| -rw-r--r-- | src/platform/wayland/shell.c | 7 | ||||
| -rw-r--r-- | src/platform/wayland/surface.c | 7 | ||||
| -rw-r--r-- | src/platform/x11/window.c | 504 |
13 files changed, 930 insertions, 197 deletions
diff --git a/src/platform/cocoa/window.m b/src/platform/cocoa/window.m index 2f9f8e5..60b20b0 100644 --- a/src/platform/cocoa/window.m +++ b/src/platform/cocoa/window.m @@ -12,6 +12,7 @@ #include "keymap.h" #include <assert.h> +#include <errno.h> #include <math.h> #include <poll.h> #include <stdio.h> @@ -172,11 +173,13 @@ static void drag_end(struct cocoa_display *d, enum yasl_drag_end how) [d->drag.image release]; d->drag.image = nil; - if (hover && !hover->destroyed && hover->cbs && hover->cbs->drag) { + if (hover && !hover->destroyed) { + darwin_ctx.depth++; if (how == YASL_DRAG_DROPPED) - hover->cbs->drag(&hover->base, YASL_DRAG_DROP, pos, hover->ud); + yasl_surface_notify_drag(&hover->base, YASL_DRAG_DROP, pos); else - hover->cbs->drag(&hover->base, YASL_DRAG_LEAVE, pos, hover->ud); + yasl_surface_notify_drag(&hover->base, YASL_DRAG_LEAVE, pos); + darwin_ctx.depth--; } if (l.end) l.end(ud, how); @@ -187,15 +190,14 @@ static void reap_dead(struct cocoa_display *d); static void set_state(struct cocoa_surface *w, uint32_t bit, bool on) { - if (!w || w->closed) + if (!w || w->destroyed || w->closed) return; uint32_t next = on ? (w->base.state | bit) : (w->base.state & ~bit); if (next == w->base.state) return; w->base.state = next; darwin_ctx.depth++; - if (w->cbs->state) - w->cbs->state(&w->base, next, w->ud); + yasl_surface_notify_state(&w->base, next); darwin_ctx.depth--; } @@ -227,13 +229,14 @@ static void push_focus(struct cocoa_surface *w, bool focused) input_queue_push(&w->input, &ev); darwin_ctx.depth++; set_state(w, YASL_STATE_ACTIVATED, focused); - cocoa_surface_schedule_redraw(w); + if (!w->destroyed && !w->closed) + cocoa_surface_schedule_redraw(w); darwin_ctx.depth--; } static void sync_size(struct cocoa_surface *w, bool force) { - if (!w || !w->layer || w->closed) + if (!w || w->destroyed || !w->layer || w->closed) return; uint64_t start = yasl_loop_now_ns(); NSRect bounds = w->view.bounds; @@ -254,14 +257,15 @@ static void sync_size(struct cocoa_surface *w, bool force) w->scale_120 = scale_120; w->layer.contentsScale = scale; w->layer.drawableSize = CGSizeMake(size.x, size.y); - if (w->cbs->configure) - w->cbs->configure(&w->base, (struct yasl_geometry){ + /* Common configure notification already requests a frame before invoking + * the legacy callback. Finish local bookkeeping before that callback can + * destroy this surface. */ + yasl_clock_note_resize(&w->clock, yasl_loop_now_ns() - start); + yasl_surface_notify_configure(&w->base, (struct yasl_geometry){ .size = size, .scale_120 = scale_120, - }, w->ud); - cocoa_surface_schedule_redraw(w); - if (w->view.inLiveResize) + }); + if (!w->destroyed && !w->closed && w->view.inLiveResize) render_and_commit(w); - yasl_clock_note_resize(&w->clock, yasl_loop_now_ns() - start); darwin_ctx.depth--; } @@ -427,19 +431,26 @@ static void update_drag_hover(struct cocoa_display *d, NSPoint screenPoint) struct cocoa_surface *old = d->drag.hover; struct yasl_vector2i old_pos = d->drag.hover_pos; d->drag.hover = NULL; - if (old->cbs && old->cbs->drag && !old->destroyed) - old->cbs->drag(&old->base, YASL_DRAG_LEAVE, old_pos, old->ud); + if (!old->destroyed) { + darwin_ctx.depth++; + yasl_surface_notify_drag(&old->base, YASL_DRAG_LEAVE, old_pos); + darwin_ctx.depth--; + } if (!d->drag.active) return; } if (target && target->destroyed) target = NULL; d->drag.hover = target; d->drag.hover_pos = pos; - if (target && !target->destroyed && target->cbs && target->cbs->drag) - target->cbs->drag(&target->base, YASL_DRAG_OVER, pos, target->ud); + if (target && !target->destroyed) { + darwin_ctx.depth++; + yasl_surface_notify_drag(&target->base, YASL_DRAG_OVER, pos); + darwin_ctx.depth--; + } } else if (target && !target->destroyed) { d->drag.hover_pos = pos; - if (target->cbs && target->cbs->drag) - target->cbs->drag(&target->base, YASL_DRAG_OVER, pos, target->ud); + darwin_ctx.depth++; + yasl_surface_notify_drag(&target->base, YASL_DRAG_OVER, pos); + darwin_ctx.depth--; } } @@ -559,15 +570,14 @@ static NSCursor *cocoa_cursor(enum yasl_cursor cursor) - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender { (void)sender; - if (darwin_ctx.cbs && darwin_ctx.cbs->quit) - darwin_ctx.cbs->quit(darwin_ctx.ud); + yasl_display_notify_event(&darwin_ctx.base, YASL_EVENT_DISPLAY_QUIT); return NSTerminateCancel; } - (BOOL)applicationShouldHandleReopen:(NSApplication *)sender hasVisibleWindows:(BOOL)visible { (void)sender; - if (!visible && darwin_ctx.cbs && darwin_ctx.cbs->reopen) - darwin_ctx.cbs->reopen(darwin_ctx.ud); + if (!visible) + yasl_display_notify_event(&darwin_ctx.base, YASL_EVENT_DISPLAY_REOPEN); return YES; } - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender @@ -608,9 +618,11 @@ static struct yasl_vector2i dragging_pos(YaslView *view, id<NSDraggingInfo> send NSPasteboard *pb = [sender draggingPasteboard]; if ([pb canReadItemWithDataConformingToTypes:@[NSPasteboardTypeFileURL]]) { struct cocoa_surface *w = self.surface; - if (w && w->cbs && w->cbs->drop) { + if (w && !w->closed) { struct yasl_vector2i pos = dragging_pos(self, sender); - w->cbs->drop(&w->base, YASL_DROP_OVER, pos, w->ud); + darwin_ctx.depth++; + yasl_surface_notify_drop(&w->base, YASL_DROP_OVER, pos); + darwin_ctx.depth--; } return NSDragOperationCopy; } @@ -627,9 +639,11 @@ static struct yasl_vector2i dragging_pos(YaslView *view, id<NSDraggingInfo> send NSPasteboard *pb = [sender draggingPasteboard]; if ([pb canReadItemWithDataConformingToTypes:@[NSPasteboardTypeFileURL]]) { struct cocoa_surface *w = self.surface; - if (w && w->cbs && w->cbs->drop) { + if (w && !w->closed) { struct yasl_vector2i pos = dragging_pos(self, sender); - w->cbs->drop(&w->base, YASL_DROP_LEAVE, pos, w->ud); + darwin_ctx.depth++; + yasl_surface_notify_drop(&w->base, YASL_DROP_LEAVE, pos); + darwin_ctx.depth--; } } } @@ -662,8 +676,11 @@ static struct yasl_vector2i dragging_pos(YaslView *view, id<NSDraggingInfo> send ok = YES; } } - if (w->cbs && w->cbs->drop) - w->cbs->drop(&w->base, YASL_DROP_LEAVE, pos, w->ud); + if (!w->destroyed) { + darwin_ctx.depth++; + yasl_surface_notify_drop(&w->base, YASL_DROP_LEAVE, pos); + darwin_ctx.depth--; + } return ok; } @@ -869,13 +886,19 @@ static NSString *plain_string(id string) { (void)replacementRange; struct cocoa_surface *w = self.surface; - if (!w) return; + if (!w || w->destroyed || w->closed) return; const char *utf8 = plain_string(string).UTF8String; size_t len = utf8 ? strlen(utf8) : 0; + bool had_preedit = w->preedit != NULL; free(w->preedit); w->preedit = NULL; + darwin_ctx.depth++; on_text_input(w, utf8, len); - cocoa_surface_schedule_redraw(w); + if (had_preedit) + yasl_surface_notify_composition(&w->base, NULL, -1, -1); + if (!w->destroyed && !w->closed) + cocoa_surface_schedule_redraw(w); + darwin_ctx.depth--; } - (void)doCommandBySelector:(SEL)selector { (void)selector; } @@ -883,24 +906,43 @@ static NSString *plain_string(id string) - (void)setMarkedText:(id)string selectedRange:(NSRange)selectedRange replacementRange:(NSRange)replacementRange { - (void)selectedRange; (void)replacementRange; struct cocoa_surface *w = self.surface; - if (!w) return; + if (!w || w->destroyed || w->closed) return; NSString *str = plain_string(string); const char *utf8 = str.length ? str.UTF8String : NULL; free(w->preedit); w->preedit = utf8 ? strdup(utf8) : NULL; - cocoa_surface_schedule_redraw(w); + int begin = -1, end = -1; + if (w->preedit && selectedRange.location != NSNotFound && + selectedRange.location <= str.length && selectedRange.length <= str.length - selectedRange.location) { + NSString *prefix = [str substringToIndex:selectedRange.location]; + NSString *selection = [str substringWithRange:selectedRange]; + const char *prefix_utf8 = prefix.UTF8String; + const char *selection_utf8 = selection.UTF8String; + if (prefix_utf8 && selection_utf8) { + begin = (int)strlen(prefix_utf8); + end = begin + (int)strlen(selection_utf8); + } + } + darwin_ctx.depth++; + yasl_surface_notify_composition(&w->base, w->preedit, begin, end); + if (!w->destroyed && !w->closed) + cocoa_surface_schedule_redraw(w); + darwin_ctx.depth--; } - (void)unmarkText { struct cocoa_surface *w = self.surface; - if (!w || !w->preedit) return; + if (!w || w->destroyed || w->closed || !w->preedit) return; free(w->preedit); w->preedit = NULL; - cocoa_surface_schedule_redraw(w); + darwin_ctx.depth++; + yasl_surface_notify_composition(&w->base, NULL, -1, -1); + if (!w->destroyed && !w->closed) + cocoa_surface_schedule_redraw(w); + darwin_ctx.depth--; } - (NSRange)selectedRange { return NSMakeRange(NSNotFound, 0); } @@ -971,10 +1013,12 @@ static NSString *plain_string(id string) bool visible = (self.window.occlusionState & NSWindowOcclusionStateVisible) != 0; darwin_ctx.depth++; set_state(w, YASL_STATE_SUSPENDED, !visible); - if (visible) - cocoa_surface_schedule_redraw(w); - else - w->link.paused = YES; + if (!w->destroyed && !w->closed) { + if (visible) + cocoa_surface_schedule_redraw(w); + else + w->link.paused = YES; + } darwin_ctx.depth--; } @@ -1031,8 +1075,7 @@ static NSString *plain_string(id string) darwin_ctx.depth++; w->closed = true; [w->link invalidate]; - if (w->cbs->close) - w->cbs->close(&w->base, w->ud); + yasl_surface_notify_close(&w->base); darwin_ctx.depth--; } @@ -1081,7 +1124,7 @@ bool cocoa_surface_present(struct cocoa_surface *w, const struct yasl_framebuffe static void render_and_commit(struct cocoa_surface *w) { - if (w->closed || !w->cbs->draw || w->rendering || w->size.x <= 0 || w->size.y <= 0 || + if (w->destroyed || w->closed || !w->cbs->draw || w->rendering || w->size.x <= 0 || w->size.y <= 0 || (w->base.state & YASL_STATE_SUSPENDED)) return; @@ -1110,8 +1153,8 @@ static void render_and_commit(struct cocoa_surface *w) }, }; if (drawable) - w->cbs->draw(&w->base, &w->frame, w->ud); - if (!w->closed && !w->did_present) { + yasl_surface_draw(&w->base, &w->frame); + if (!w->destroyed && !w->closed && !w->did_present) { w->want_draw = true; w->frames_skipped++; } @@ -1122,7 +1165,7 @@ static void render_and_commit(struct cocoa_surface *w) static void pump_window(struct cocoa_surface *w) { - if (!w || w->closed || !w->want_draw || !w->cbs->draw) + if (!w || w->destroyed || w->closed || !w->want_draw || !w->cbs->draw) return; if (w->link && !w->frame_due && !w->unpaced) return; @@ -1368,6 +1411,7 @@ struct cocoa_display *cocoa_display_open(struct yasl_loop *loop, const struct ya darwin_ctx.base.callbacks = spec->cbs ? *spec->cbs : (struct yasl_display_cbs){0}; darwin_ctx.cbs = &darwin_ctx.base.callbacks; darwin_ctx.ud = spec->ud; + darwin_ctx.base.callback_ud = spec->ud; if (spec->app_id) darwin_ctx.app_id = strdup(spec->app_id); darwin_ctx.base.app_id = darwin_ctx.app_id; } @@ -1585,6 +1629,57 @@ void cocoa_surface_set_title(struct cocoa_surface *w, const char *title) w->window.title = [NSString stringWithUTF8String:w->title] ?: @"YASL"; } +static bool impl_surface_set_size(struct yasl_surface *surface, struct yasl_vector2i size) +{ + struct cocoa_surface *w = (struct cocoa_surface *)surface; + if (!w || w->closed || w->is_popup || size.x <= 0 || size.y <= 0) { + errno = EINVAL; + return false; + } + [w->window setContentSize:NSMakeSize(size.x, size.y)]; + errno = 0; + return true; +} + +static bool impl_surface_set_fullscreen(struct yasl_surface *surface, bool enabled) +{ + struct cocoa_surface *w = (struct cocoa_surface *)surface; + if (!w || w->closed || w->is_popup || !w->window) { + errno = EINVAL; + return false; + } + bool fullscreen = (w->window.styleMask & NSWindowStyleMaskFullScreen) != 0; + if (fullscreen != enabled) + [w->window toggleFullScreen:nil]; + errno = 0; + return true; +} + +static bool impl_surface_set_maximized(struct yasl_surface *surface, bool enabled) +{ + struct cocoa_surface *w = (struct cocoa_surface *)surface; + if (!w || w->closed || w->is_popup || !w->window) { + errno = EINVAL; + return false; + } + if (w->window.isZoomed != enabled) + [w->window zoom:nil]; + errno = 0; + return true; +} + +static bool impl_surface_minimize(struct yasl_surface *surface) +{ + struct cocoa_surface *w = (struct cocoa_surface *)surface; + if (!w || w->closed || w->is_popup || !w->window) { + errno = EINVAL; + return false; + } + [w->window miniaturize:nil]; + errno = 0; + return true; +} + void cocoa_surface_set_background_color(struct cocoa_surface *w, uint32_t bg) { if (!w || w->closed) return; @@ -1644,34 +1739,54 @@ static NSPasteboard *pasteboard_for_selection(enum yasl_selection sel) } } +static bool cocoa_display_copy_text(struct cocoa_display *d, enum yasl_selection sel, const char *text); + void cocoa_display_copy(struct cocoa_display *d, enum yasl_selection sel, const char *text) { - (void)d; - if (!text) return; + (void)cocoa_display_copy_text(d, sel, text); +} + +static bool cocoa_display_copy_text(struct cocoa_display *d, enum yasl_selection sel, const char *text) +{ + assert([NSThread isMainThread]); + if (!d || !text) { errno = EINVAL; return false; } NSPasteboard *pb = pasteboard_for_selection(sel); - if (!pb) return; + if (!pb) { errno = EINVAL; return false; } NSString *s = [NSString stringWithUTF8String:text]; - if (!s) return; + if (!s) { errno = EINVAL; return false; } [pb clearContents]; - [pb setString:s forType:NSPasteboardTypeString]; + if (![pb setString:s forType:NSPasteboardTypeString]) { + errno = EIO; + return false; + } + errno = 0; + return true; } -void cocoa_display_paste(struct cocoa_display *d, enum yasl_selection sel, struct cocoa_surface *target) +static bool cocoa_display_request_paste(struct cocoa_display *d, enum yasl_selection sel, + struct cocoa_surface *target, uint64_t id) { - (void)d; - if (!target) return; + assert([NSThread isMainThread]); + if (!d || !target || target->destroyed || target->closed || + target->base.display != &d->base) { + errno = EINVAL; + return false; + } NSPasteboard *pb = pasteboard_for_selection(sel); - if (!pb) return; + if (!pb) { errno = EINVAL; return false; } NSString *s = [pb stringForType:NSPasteboardTypeString]; const char *utf8 = s ? s.UTF8String : NULL; - if (utf8) { - struct yasl_event ev = { - .type = YASL_EVENT_PASTE, - .surface = &target->base, - .text = { .data = utf8, .len = strlen(utf8) }, - }; - input_queue_push(&target->input, &ev); - } + enum yasl_transfer_status status = !s ? YASL_TRANSFER_UNAVAILABLE : + utf8 ? YASL_TRANSFER_OK : YASL_TRANSFER_FAILED; + yasl_surface_complete_paste(&target->base, id, status, + utf8 ? utf8 : NULL, utf8 ? strlen(utf8) : 0); + errno = 0; + return true; +} + +void cocoa_display_paste(struct cocoa_display *d, enum yasl_selection sel, struct cocoa_surface *target) +{ + (void)cocoa_display_request_paste(d, sel, target, 0); } int cocoa_display_modifiers(const struct cocoa_display *d) @@ -1990,6 +2105,7 @@ void cocoa_surface_set_callbacks(struct cocoa_surface *w, { if (!w || w->closed) return; w->base.callbacks = cbs ? *cbs : (struct yasl_window_cbs){0}; + w->base.ud = ud; w->cbs = &w->base.callbacks; w->ud = ud; } @@ -2071,6 +2187,18 @@ static void impl_display_paste(struct yasl_display *d, enum yasl_selection sel, cocoa_display_paste((struct cocoa_display *)d, sel, (struct cocoa_surface *)target); } +static bool impl_display_copy_text(struct yasl_display *d, enum yasl_selection sel, const char *text) +{ + return cocoa_display_copy_text((struct cocoa_display *)d, sel, text); +} + +static bool impl_display_request_paste(struct yasl_display *d, enum yasl_selection sel, + struct yasl_surface *target, uint64_t request_id) +{ + return cocoa_display_request_paste((struct cocoa_display *)d, sel, + (struct cocoa_surface *)target, request_id); +} + static bool impl_display_drag_start(struct yasl_display *d, struct yasl_surface *origin, const struct yasl_drag_image *image, const struct yasl_drag_listener *l, void *ud) { return cocoa_display_drag_start((struct cocoa_display *)d, (struct cocoa_surface *)origin, image, l, ud); @@ -2104,6 +2232,12 @@ static void impl_surface_destroy(struct yasl_surface *s) cocoa_surface_destroy((struct cocoa_surface *)s); } +static struct input_queue *impl_surface_input_queue(struct yasl_surface *s) +{ + struct cocoa_surface *w = (struct cocoa_surface *)s; + return w ? &w->input : NULL; +} + static struct yasl_display *impl_surface_display(const struct yasl_surface *s) { return s ? &darwin_ctx.base : NULL; @@ -2129,11 +2263,6 @@ static bool impl_surface_present(struct yasl_surface *s, const struct yasl_frame return cocoa_surface_present((struct cocoa_surface *)s, fb, damage); } -static bool impl_surface_poll_event(struct yasl_surface *s, struct yasl_event *ev) -{ - return cocoa_surface_poll_event((struct cocoa_surface *)s, ev); -} - static struct yasl_geometry impl_surface_geometry(const struct yasl_surface *s) { return cocoa_surface_geometry((const struct cocoa_surface *)s); @@ -2254,18 +2383,24 @@ const struct yasl_backend_ops yasl_cocoa_backend = { .display_double_click_ms = impl_display_double_click_ms, .display_copy = impl_display_copy, .display_paste = impl_display_paste, + .display_copy_text = impl_display_copy_text, + .display_request_paste = impl_display_request_paste, .display_drag_start = impl_display_drag_start, .display_drag_cancel = impl_display_drag_cancel, .surface_create_toplevel = impl_surface_create_toplevel, .surface_create_popup = impl_surface_create_popup, .surface_popup_reposition = impl_surface_popup_reposition, .surface_destroy = impl_surface_destroy, + .surface_input_queue = impl_surface_input_queue, + .surface_set_size = impl_surface_set_size, + .surface_set_fullscreen = impl_surface_set_fullscreen, + .surface_set_maximized = impl_surface_set_maximized, + .surface_minimize = impl_surface_minimize, .surface_display = impl_surface_display, .surface_set_callbacks = impl_surface_set_callbacks, .surface_set_title = impl_surface_set_title, .surface_schedule_redraw = impl_surface_schedule_redraw, .surface_present = impl_surface_present, - .surface_poll_event = impl_surface_poll_event, .surface_geometry = impl_surface_geometry, .surface_timing = impl_surface_timing, .surface_frame_time_ns = impl_surface_frame_time_ns, diff --git a/src/platform/wayland/api.h b/src/platform/wayland/api.h index 9f290f5..0f935b2 100644 --- a/src/platform/wayland/api.h +++ b/src/platform/wayland/api.h @@ -14,6 +14,9 @@ void wayland_display_flush(struct wayland_display *d); int wayland_display_modifiers(const struct wayland_display *d); void wayland_display_copy(struct wayland_display *d, enum yasl_selection sel, const char *text); void wayland_display_paste(struct wayland_display *d, enum yasl_selection sel, struct wayland_surface *target); +bool wayland_display_copy_text(struct wayland_display *d, enum yasl_selection sel, const char *text); +bool wayland_display_request_paste(struct wayland_display *d, enum yasl_selection sel, + struct wayland_surface *target, uint64_t request_id); bool wayland_display_drag_start(struct wayland_display *d, struct wayland_surface *origin, const struct yasl_drag_image *image, const struct yasl_drag_listener *l, void *ud); void wayland_display_drag_cancel(struct wayland_display *d); struct wayland_surface *wayland_surface_create_toplevel(struct wayland_display *d, diff --git a/src/platform/wayland/backend.c b/src/platform/wayland/backend.c index 4585cd6..7026a8c 100644 --- a/src/platform/wayland/backend.c +++ b/src/platform/wayland/backend.c @@ -1,5 +1,6 @@ #include "platform/wayland/internal.h" #include "platform/freedesktop/dbus.h" +#include <errno.h> static struct yasl_display *impl_display_open(struct yasl_loop *loop, const struct yasl_display_spec *spec) { @@ -44,11 +45,28 @@ static void impl_display_copy(struct yasl_display *d, enum yasl_selection sel, c wayland_display_copy((struct wayland_display *)d, sel, text); } +static bool impl_display_copy_text(struct yasl_display *d, enum yasl_selection sel, + const char *text) +{ + bool ok = wayland_display_copy_text((struct wayland_display *)d, sel, text); + if (!ok && errno == 0) errno = ENOTSUP; + return ok; +} + static void impl_display_paste(struct yasl_display *d, enum yasl_selection sel, struct yasl_surface *target) { wayland_display_paste((struct wayland_display *)d, sel, (struct wayland_surface *)target); } +static bool impl_display_request_paste(struct yasl_display *d, enum yasl_selection sel, + struct yasl_surface *target, uint64_t request_id) +{ + bool ok = wayland_display_request_paste((struct wayland_display *)d, sel, + (struct wayland_surface *)target, request_id); + if (!ok && errno == 0) errno = ENOTSUP; + return ok; +} + static bool impl_display_drag_start(struct yasl_display *d, struct yasl_surface *origin, const struct yasl_drag_image *image, const struct yasl_drag_listener *l, void *ud) { return wayland_display_drag_start((struct wayland_display *)d, (struct wayland_surface *)origin, image, l, ud); @@ -82,6 +100,59 @@ static void impl_surface_destroy(struct yasl_surface *s) wayland_surface_destroy((struct wayland_surface *)s); } +static struct input_queue *impl_surface_input_queue(struct yasl_surface *s) +{ + struct wayland_surface *w = (struct wayland_surface *)s; + return w ? &w->input : NULL; +} + +static bool impl_surface_set_size(struct yasl_surface *s, struct yasl_vector2i size) +{ + struct wayland_surface *w = (struct wayland_surface *)s; + if (!w || w->closed || w->destroyed || !w->toplevel || + size.x <= 0 || size.y <= 0) { + errno = EINVAL; + return false; + } + errno = ENOTSUP; + return false; +} + +static bool impl_surface_set_fullscreen(struct yasl_surface *s, bool enabled) +{ + struct wayland_surface *w = (struct wayland_surface *)s; + if (!w || w->closed || w->destroyed || !w->toplevel) { + errno = EINVAL; + return false; + } + if (enabled) xdg_toplevel_set_fullscreen(w->toplevel, NULL); + else xdg_toplevel_unset_fullscreen(w->toplevel); + return true; +} + +static bool impl_surface_set_maximized(struct yasl_surface *s, bool enabled) +{ + struct wayland_surface *w = (struct wayland_surface *)s; + if (!w || w->closed || w->destroyed || !w->toplevel) { + errno = EINVAL; + return false; + } + if (enabled) xdg_toplevel_set_maximized(w->toplevel); + else xdg_toplevel_unset_maximized(w->toplevel); + return true; +} + +static bool impl_surface_minimize(struct yasl_surface *s) +{ + struct wayland_surface *w = (struct wayland_surface *)s; + if (!w || w->closed || w->destroyed || !w->toplevel) { + errno = EINVAL; + return false; + } + xdg_toplevel_set_minimized(w->toplevel); + return true; +} + static struct yasl_display *impl_surface_display(const struct yasl_surface *s) { const struct wayland_surface *w = (const struct wayland_surface *)s; @@ -219,12 +290,19 @@ const struct yasl_backend_ops yasl_wayland_backend = { .display_double_click_ms = impl_display_double_click_ms, .display_copy = impl_display_copy, .display_paste = impl_display_paste, + .display_copy_text = impl_display_copy_text, + .display_request_paste = impl_display_request_paste, .display_drag_start = impl_display_drag_start, .display_drag_cancel = impl_display_drag_cancel, .surface_create_toplevel = impl_surface_create_toplevel, .surface_create_popup = impl_surface_create_popup, .surface_popup_reposition = impl_surface_popup_reposition, .surface_destroy = impl_surface_destroy, + .surface_input_queue = impl_surface_input_queue, + .surface_set_size = impl_surface_set_size, + .surface_set_fullscreen = impl_surface_set_fullscreen, + .surface_set_maximized = impl_surface_set_maximized, + .surface_minimize = impl_surface_minimize, .surface_display = impl_surface_display, .surface_set_callbacks = impl_surface_set_callbacks, .surface_set_title = impl_surface_set_title, diff --git a/src/platform/wayland/buffer.c b/src/platform/wayland/buffer.c index b906f4f..874cc76 100644 --- a/src/platform/wayland/buffer.c +++ b/src/platform/wayland/buffer.c @@ -182,10 +182,13 @@ int apply_pending_geometry(struct wayland_surface *w) w->buffers[i].presented = false; } publish_surface_scale(w); - if (w->cbs->configure && (resized || rescaled)) - w->cbs->configure(&w->base, (struct yasl_geometry){ + if (resized || rescaled) { + yasl_surface_notify_configure(&w->base, (struct yasl_geometry){ .size = { .x = width, .y = height }, .scale_120 = w->scale_120, - }, w->ud); + }); + if (w->closed || w->destroyed) + return -1; + } return 0; } @@ -525,9 +528,11 @@ void surface_draw_frame(struct wayland_surface *w) }; w->frame_active = true; w->frame_presented = false; - w->cbs->draw(&w->base, &w->frame, w->ud); + yasl_surface_draw(&w->base, &w->frame); + if (w->closed || w->destroyed) + return; w->frame_active = false; - if (!w->closed && !w->frame_presented) + if (!w->frame_presented) w->frames_skipped++; } diff --git a/src/platform/wayland/clipboard.c b/src/platform/wayland/clipboard.c index 353e993..8ca9b93 100644 --- a/src/platform/wayland/clipboard.c +++ b/src/platform/wayland/clipboard.c @@ -5,6 +5,7 @@ #include <errno.h> #define CLIPBOARD_TEXT_MAX (256u * 1024) +#define CLIPBOARD_TRANSFER_TIMEOUT_NS (30ull * 1000000000ull) struct offer { struct offer *next; @@ -24,10 +25,14 @@ struct transfer { struct offer *drop; struct wl_surface *drop_surface; int fd, id; + yasl_timer_id timeout; char *text; size_t len, offset, capacity; + uint64_t request_id; bool sending; bool uri_list; + bool paste; + bool cancelled; }; struct clipboard { struct wl_display *display; @@ -82,20 +87,48 @@ static void drop_event(struct clipboard *c, enum yasl_drop_event ev) static void free_offer(struct offer *o); +static void transfer_end(struct transfer *t, bool complete); + +static uint64_t transfer_timeout(void *ud, yasl_timer_id id, uint64_t interval) +{ + (void)interval; + struct transfer *t = ud; + if (t->timeout != id) + return 0; + t->timeout = 0; + transfer_end(t, false); + return 0; +} + static void transfer_end(struct transfer *t, bool complete) { - struct transfer **p = &t->owner->transfers; + struct clipboard *owner = t->owner; + struct transfer **p = &owner->transfers; while (*p && *p != t) p = &(*p)->next; if (*p) *p = t->next; - yasl_loop_del_fd(t->owner->loop, t->id); + yasl_loop_del_fd(owner->loop, t->id); + if (t->timeout) + yasl_loop_del_timer(owner->loop, t->timeout); close(t->fd); if (t->drop) { if (complete && wl_data_offer_get_version(t->drop->proxy) >= WL_DATA_OFFER_FINISH_SINCE_VERSION) wl_data_offer_finish(t->drop->proxy); free_offer(t->drop); - wl_display_flush(t->owner->display); + wl_display_flush(owner->display); } + bool notify = owner->receive && !t->cancelled && + ((!t->sending && complete) || (t->paste && !complete)); + clipboard_receive_fn receive = owner->receive; + void *receive_ud = owner->ud; + struct wl_surface *surface = t->drop_surface; + uint64_t request_id = t->request_id; + bool uri_list = t->uri_list; + enum yasl_transfer_status status = complete ? YASL_TRANSFER_OK : YASL_TRANSFER_FAILED; + const char *text = complete ? (t->text ? t->text : "") : NULL; + size_t len = complete ? t->len : 0; + if (notify) + receive(text, len, uri_list, surface, request_id, status, receive_ud); free(t->text); free(t); } @@ -124,9 +157,6 @@ static int transfer_io(int fd, short events, void *ud) t->len += (size_t)n; } else if (n == 0) { bool valid = yasl_utf8_validate(t->text, t->len); - if (valid) - t->owner->receive(t->text ? t->text : "", t->len, t->uri_list, - t->drop_surface, t->owner->ud); transfer_end(t, valid); } else if (errno != EAGAIN && errno != EINTR) transfer_end(t, false); } @@ -148,10 +178,31 @@ static struct transfer *start_transfer_text(struct clipboard *c, int fd, bool se } t->id = yasl_loop_add_fd(c->loop, fd, sending ? POLLOUT : POLLIN, transfer_io, t); if (t->id < 0) { close(fd); free(t->text); free(t); return NULL; } + t->timeout = yasl_loop_add_timer(c->loop, CLIPBOARD_TRANSFER_TIMEOUT_NS, + transfer_timeout, t); + if (!t->timeout) { + yasl_loop_del_fd(c->loop, t->id); + close(fd); + free(t->text); + free(t); + return NULL; + } t->next = c->transfers; c->transfers = t; return t; } +static struct transfer *start_transfer_paste(struct clipboard *c, int fd, + struct wl_surface *target, uint64_t request_id) +{ + struct transfer *t = start_transfer_text(c, fd, false, false, NULL); + if (t) { + t->drop_surface = target; + t->request_id = request_id; + t->paste = true; + } + return t; +} + static const char *match_text_mime(const char *mime) { if (unlikely(!mime)) return NULL; @@ -528,15 +579,17 @@ bool clipboard_set_seat(struct clipboard *c, struct wl_seat *seat) return true; } -void clipboard_copy(struct clipboard *c, const char *text, uint32_t serial) +bool clipboard_copy(struct clipboard *c, const char *text, uint32_t serial) { - if (!c || !c->device || !text || !serial) return; + if (!c || !c->device) { errno = ENOTSUP; return false; } + if (!text) { errno = EINVAL; return false; } + if (!serial) { errno = EAGAIN; return false; } char *copy = strdup(text); - if (!copy) return; + if (!copy) { errno = ENOMEM; return false; } if (c->source) wl_data_source_destroy(c->source); free(c->text); c->text = copy; c->source = wl_data_device_manager_create_data_source(c->manager); - if (!c->source) return; + if (!c->source) { errno = ENOMEM; return false; } wl_data_source_add_listener(c->source, &source_listener, c); wl_data_source_offer(c->source, "text/plain;charset=utf-8"); wl_data_source_offer(c->source, "text/plain"); @@ -544,30 +597,26 @@ void clipboard_copy(struct clipboard *c, const char *text, uint32_t serial) wl_data_source_offer(c->source, "STRING"); wl_data_source_offer(c->source, "TEXT"); wl_data_device_set_selection(c->device, c->source, serial); + return true; } void clipboard_paste(struct clipboard *c, struct wl_surface *target) { - if (!c || !c->device || !c->selection || !c->selection->mime) return; - int fds[2]; - if (pipe2(fds, O_CLOEXEC) < 0) return; - wl_data_offer_receive(c->selection->proxy, c->selection->mime, fds[1]); - close(fds[1]); - struct transfer *t = start_transfer_text(c, fds[0], false, false, c->text); - if (t) t->drop_surface = target; - wl_display_flush(c->display); + if (c && target) + (void)clipboard_request_paste(c, YASL_CLIPBOARD, target, 0); } -void clipboard_copy_primary(struct clipboard *c, const char *text, uint32_t serial) +bool clipboard_copy_primary(struct clipboard *c, const char *text, uint32_t serial) { - if (!c || !text || !serial) return; - if (!c->primary_device || !c->primary_manager) return; + if (!c || !c->primary_device || !c->primary_manager) { errno = ENOTSUP; return false; } + if (!text) { errno = EINVAL; return false; } + if (!serial) { errno = EAGAIN; return false; } char *copy = strdup(text); - if (!copy) return; + if (!copy) { errno = ENOMEM; return false; } if (c->primary_source) zwp_primary_selection_source_v1_destroy(c->primary_source); free(c->primary_text); c->primary_text = copy; c->primary_source = zwp_primary_selection_device_manager_v1_create_source(c->primary_manager); - if (!c->primary_source) return; + if (!c->primary_source) { errno = ENOMEM; return false; } zwp_primary_selection_source_v1_add_listener(c->primary_source, &primary_source_listener, c); zwp_primary_selection_source_v1_offer(c->primary_source, "text/plain;charset=utf-8"); zwp_primary_selection_source_v1_offer(c->primary_source, "text/plain"); @@ -575,22 +624,58 @@ void clipboard_copy_primary(struct clipboard *c, const char *text, uint32_t seri zwp_primary_selection_source_v1_offer(c->primary_source, "STRING"); zwp_primary_selection_source_v1_offer(c->primary_source, "TEXT"); zwp_primary_selection_device_v1_set_selection(c->primary_device, c->primary_source, serial); + return true; } void clipboard_paste_primary(struct clipboard *c, struct wl_surface *target) { - if (!c) return; - if (c->primary_device && c->primary_selection && c->primary_selection->mime) { - int fds[2]; - if (pipe2(fds, O_CLOEXEC) < 0) return; + if (c && target) + (void)clipboard_request_paste(c, YASL_PRIMARY, target, 0); +} + +bool clipboard_request_paste(struct clipboard *c, enum yasl_selection selection, + struct wl_surface *target, uint64_t request_id) +{ + if (!c || !target || !c->receive) + return false; + int fds[2]; + if (selection == YASL_PRIMARY) { + if (!c->primary_device || !c->primary_selection || !c->primary_selection->mime) { + c->receive(NULL, 0, false, target, request_id, + YASL_TRANSFER_UNAVAILABLE, c->ud); + return true; + } + if (pipe2(fds, O_CLOEXEC) < 0) { + c->receive(NULL, 0, false, target, request_id, + YASL_TRANSFER_FAILED, c->ud); + return true; + } zwp_primary_selection_offer_v1_receive(c->primary_selection->proxy, c->primary_selection->mime, fds[1]); close(fds[1]); - struct transfer *t = start_transfer_text(c, fds[0], false, false, c->text); - if (t) t->drop_surface = target; - wl_display_flush(c->display); - return; + } else { + if (!c->device || !c->selection || !c->selection->mime) { + c->receive(NULL, 0, false, target, request_id, + YASL_TRANSFER_UNAVAILABLE, c->ud); + return true; + } + if (pipe2(fds, O_CLOEXEC) < 0) { + c->receive(NULL, 0, false, target, request_id, + YASL_TRANSFER_FAILED, c->ud); + return true; + } + wl_data_offer_receive(c->selection->proxy, c->selection->mime, fds[1]); + close(fds[1]); + } + struct transfer *transfer = start_transfer_paste(c, fds[0], target, request_id); + if (!transfer) { + c->receive(NULL, 0, false, target, request_id, + YASL_TRANSFER_FAILED, c->ud); + return true; } + if (wl_display_flush(c->display) < 0 && errno != EAGAIN) + transfer_end(transfer, false); + return true; } void clipboard_forget_surface(struct clipboard *c, struct wl_surface *surface) @@ -598,7 +683,10 @@ void clipboard_forget_surface(struct clipboard *c, struct wl_surface *surface) if (!c || !surface) return; for (struct transfer *t = c->transfers, *next; t; t = next) { next = t->next; - if (t->drop_surface == surface) transfer_end(t, false); + if (t->drop_surface == surface) { + t->cancelled = true; + transfer_end(t, false); + } } if (c->drag_surface == surface) c->drag_surface = NULL; } diff --git a/src/platform/wayland/clipboard.h b/src/platform/wayland/clipboard.h index 9fefa8e..133cc14 100644 --- a/src/platform/wayland/clipboard.h +++ b/src/platform/wayland/clipboard.h @@ -15,7 +15,8 @@ struct clipboard; struct zwp_primary_selection_device_manager_v1; typedef void (*clipboard_receive_fn)(const char *, size_t, bool uri_list, - struct wl_surface *on, void *); + struct wl_surface *on, uint64_t request_id, + enum yasl_transfer_status, void *); struct clipboard *clipboard_create(struct wl_display *, struct wl_data_device_manager *, struct wl_seat *, struct yasl_loop *, @@ -23,10 +24,12 @@ struct clipboard *clipboard_create(struct wl_display *, struct wl_data_device_ma void clipboard_destroy(struct clipboard *); bool clipboard_set_seat(struct clipboard *, struct wl_seat *); void clipboard_set_primary_manager(struct clipboard *, struct zwp_primary_selection_device_manager_v1 *); -void clipboard_copy(struct clipboard *, const char *, uint32_t); +bool clipboard_copy(struct clipboard *, const char *, uint32_t); void clipboard_paste(struct clipboard *, struct wl_surface *target); +bool clipboard_request_paste(struct clipboard *, enum yasl_selection, + struct wl_surface *target, uint64_t request_id); void clipboard_forget_surface(struct clipboard *, struct wl_surface *); -void clipboard_copy_primary(struct clipboard *, const char *, uint32_t); +bool clipboard_copy_primary(struct clipboard *, const char *, uint32_t); void clipboard_paste_primary(struct clipboard *, struct wl_surface *target); typedef void (*clipboard_drag_fn)(enum yasl_drag_event, struct wl_surface *on, diff --git a/src/platform/wayland/drag.c b/src/platform/wayland/drag.c index 2cfd432..1f4a78b 100644 --- a/src/platform/wayland/drag.c +++ b/src/platform/wayland/drag.c @@ -228,9 +228,9 @@ void conn_drop_target(struct wayland_display *c, enum yasl_drop_event ev, (void)c; struct wayland_surface *w = on ? (struct wayland_surface *)wl_surface_get_user_data(on) : NULL; - if (!w || w->parent || !w->cbs->drop) + if (!w || w->parent || w->closed) return; - w->cbs->drop(&w->base, ev, drag_device_pos(w, x, y), w->ud); + yasl_surface_notify_drop(&w->base, ev, drag_device_pos(w, x, y)); } void conn_drag_target(struct wayland_display *c, enum yasl_drag_event ev, @@ -239,7 +239,7 @@ void conn_drag_target(struct wayland_display *c, enum yasl_drag_event ev, (void)c; struct wayland_surface *w = on ? (struct wayland_surface *)wl_surface_get_user_data(on) : NULL; - if (!w || w->parent || !w->cbs->drag) + if (!w || w->parent || w->closed) return; - w->cbs->drag(&w->base, ev, drag_device_pos(w, x, y), w->ud); + yasl_surface_notify_drag(&w->base, ev, drag_device_pos(w, x, y)); } diff --git a/src/platform/wayland/ime.c b/src/platform/wayland/ime.c index 9f1b27d..4a091d8 100644 --- a/src/platform/wayland/ime.c +++ b/src/platform/wayland/ime.c @@ -11,8 +11,11 @@ static void show_preedit(struct wayland_surface *w, char *text, int begin, int e w->ime_shown = text; w->ime_shown_begin = begin; w->ime_shown_end = end; - if (changed) + if (changed) { + yasl_surface_notify_composition(&w->base, w->ime_shown, + w->ime_shown_begin, w->ime_shown_end); wayland_surface_schedule_redraw(w); + } } void ime_reset(struct wayland_surface *w) diff --git a/src/platform/wayland/keyboard.c b/src/platform/wayland/keyboard.c index 038ba78..7f8274d 100644 --- a/src/platform/wayland/keyboard.c +++ b/src/platform/wayland/keyboard.c @@ -1,7 +1,7 @@ #include "internal.h" +#include <errno.h> #include "common/keyboard.h" #include "keymap.h" -#include <errno.h> #define XKB_EVDEV_OFFSET 8 #define KEYMAP_MAX_BYTES (8u * 1024 * 1024) @@ -427,27 +427,65 @@ int wayland_display_modifiers(const struct wayland_display *c) void wayland_display_copy(struct wayland_display *c, enum yasl_selection sel, const char *text) { + (void)wayland_display_copy_text(c, sel, text); +} + +bool wayland_display_copy_text(struct wayland_display *c, enum yasl_selection sel, + const char *text) +{ struct seat *s = c ? (c->active_seat ? c->active_seat : c->seats) : NULL; - if (!s || !s->clipboard) - return; - if (sel == YASL_PRIMARY) - clipboard_copy_primary(s->clipboard, text, s->input_serial); - else + if (!text) { + errno = EINVAL; + return false; + } + if (!s || !s->clipboard) { + errno = ENOTSUP; + return false; + } + if (!s->input_serial) { + errno = EAGAIN; + return false; + } + errno = 0; + bool ok = sel == YASL_PRIMARY ? + clipboard_copy_primary(s->clipboard, text, s->input_serial) : clipboard_copy(s->clipboard, text, s->input_serial); + return ok; } void wayland_display_paste(struct wayland_display *c, enum yasl_selection sel, struct wayland_surface *target) { struct seat *s = c ? (c->active_seat ? c->active_seat : c->seats) : NULL; - if (!s || !s->clipboard) + if (!target || target->closed || target->destroyed) return; + if (!s || !s->clipboard) { + yasl_surface_complete_paste(&target->base, 0, + YASL_TRANSFER_UNAVAILABLE, NULL, 0); return; - if (!target || target->closed) return; + } if (sel == YASL_PRIMARY) clipboard_paste_primary(s->clipboard, target->surface); else clipboard_paste(s->clipboard, target->surface); } +bool wayland_display_request_paste(struct wayland_display *c, enum yasl_selection sel, + struct wayland_surface *target, uint64_t request_id) +{ + struct seat *s = c ? (c->active_seat ? c->active_seat : c->seats) : NULL; + if (!target || target->closed || target->destroyed || !request_id) { + errno = EINVAL; + return false; + } + if (!s || !s->clipboard) { + errno = ENOTSUP; + return false; + } + errno = 0; + bool ok = clipboard_request_paste(s->clipboard, sel, target->surface, request_id); + if (!ok) errno = ENOMEM; + return ok; +} + bool wayland_surface_poll_event(struct wayland_surface *w, struct yasl_event *ev) { return w && ev && input_queue_pop(&w->input, ev); diff --git a/src/platform/wayland/pointer.c b/src/platform/wayland/pointer.c index cdc5780..da666da 100644 --- a/src/platform/wayland/pointer.c +++ b/src/platform/wayland/pointer.c @@ -1034,13 +1034,19 @@ static const struct zwp_tablet_seat_v2_listener tablet_seat_listener = { }; static void clipboard_received(const char *text, size_t len, bool uri_list, - struct wl_surface *on, void *ud) + struct wl_surface *on, uint64_t request_id, + enum yasl_transfer_status status, void *ud) { (void)ud; struct wayland_surface *w = on ? (struct wayland_surface *)wl_surface_get_user_data(on) : NULL; if (uri_list) w = surface_root(w); - if (!w || w->closed) return; + if (!w || w->closed || w->destroyed) return; + if (!uri_list) { + yasl_surface_complete_paste(&w->base, request_id, status, text, len); + return; + } + if (status != YASL_TRANSFER_OK) return; struct yasl_event ev = { .surface = &w->base, .type = uri_list ? YASL_EVENT_DROP : YASL_EVENT_PASTE, diff --git a/src/platform/wayland/shell.c b/src/platform/wayland/shell.c index 8795989..fc2ce2e 100644 --- a/src/platform/wayland/shell.c +++ b/src/platform/wayland/shell.c @@ -277,8 +277,7 @@ static void set_state(struct wayland_surface *w, uint32_t state) { if (state == w->base.state) return; w->base.state = state; - if (w->cbs && w->cbs->state) - w->cbs->state(&w->base, state, w->ud); + yasl_surface_notify_state(&w->base, state); } static void toplevel_configure(void *data, struct xdg_toplevel *toplevel, @@ -316,8 +315,8 @@ static void toplevel_close(void *data, struct xdg_toplevel *toplevel) { (void)toplevel; struct wayland_surface *w = data; - if (w && w->cbs && w->cbs->close) - w->cbs->close(&w->base, w->ud); + if (w && !w->closed) + yasl_surface_notify_close(&w->base); } static void toplevel_configure_bounds(void *data, struct xdg_toplevel *toplevel, diff --git a/src/platform/wayland/surface.c b/src/platform/wayland/surface.c index cf1877d..2e85e2b 100644 --- a/src/platform/wayland/surface.c +++ b/src/platform/wayland/surface.c @@ -52,8 +52,7 @@ static void display_lose(struct wayland_display *c) surface_fail(w); } display_leave(c); - if (c->cbs && c->cbs->lost) - c->cbs->lost(c->ud); + yasl_display_notify_event(&c->base, YASL_EVENT_DISPLAY_LOST); } void surface_fail(struct wayland_surface *w) @@ -61,8 +60,7 @@ void surface_fail(struct wayland_surface *w) if (w->closed) return; w->closed = true; - if (w->cbs->close) - w->cbs->close(&w->base, w->ud); + yasl_surface_notify_close(&w->base); } static int display_ready(int fd, short revents, void *ud) @@ -395,6 +393,7 @@ void wayland_surface_set_callbacks(struct wayland_surface *w, const struct yasl_ { if (w && !w->closed) { w->base.callbacks = cbs ? *cbs : (struct yasl_window_cbs){0}; + w->base.ud = ud; w->cbs = &w->base.callbacks; w->ud = ud; } diff --git a/src/platform/x11/window.c b/src/platform/x11/window.c index 765d83c..004d56c 100644 --- a/src/platform/x11/window.c +++ b/src/platform/x11/window.c @@ -32,8 +32,10 @@ #include <stdlib.h> #include <string.h> #include <strings.h> +#include <wchar.h> #define TEXT_LIMIT (16 * 1024 * 1024) +#define PASTE_TIMEOUT_NS YASL_MS_TO_NS(30000) #ifndef likely #define likely(x) __builtin_expect(!!(x), 1) @@ -72,8 +74,12 @@ struct x11_surface { struct paste_request { struct paste_request *next; + struct x11_display *display; struct x11_surface *target; Window window; + Atom selection; + uint64_t id; + yasl_timer_id timeout; }; struct tablet_axis { @@ -111,6 +117,7 @@ struct x11_display { bool xi2; bool has_shm; bool has_xkb; + bool lost; int xkb_event; enum yasl_key keys[256]; Cursor cursors[16]; @@ -150,6 +157,7 @@ static bool place(struct yasl_surface *base, struct yasl_vector2i at); static void drag_end(struct x11_display *d, enum yasl_drag_end how); static void drag_cancel(struct yasl_display *base); static void schedule_redraw(struct yasl_surface *base); +static uint64_t paste_timeout(void *ud, yasl_timer_id id, uint64_t interval_ns); static void leave(struct x11_display *d) { @@ -188,23 +196,23 @@ static void drag_set_hover(struct x11_display *d, struct x11_surface *s, struct struct x11_surface *old = d->drag.hover; struct yasl_vector2i old_pos = d->drag.hover_pos; d->drag.hover = NULL; - if (!old->destroyed && old->cbs && old->cbs->drag) - old->cbs->drag(&old->base, YASL_DRAG_LEAVE, old_pos, old->ud); - if (!d->drag.active) return; + if (!old->destroyed) + yasl_surface_notify_drag(&old->base, YASL_DRAG_LEAVE, old_pos); + if (!d->drag.active || (s && s->destroyed)) return; } if (s && s->destroyed) return; d->drag.hover = s; d->drag.hover_pos = pos; - if (s && s->cbs && s->cbs->drag) - s->cbs->drag(&s->base, YASL_DRAG_OVER, pos, s->ud); + if (s && !s->destroyed) + yasl_surface_notify_drag(&s->base, YASL_DRAG_OVER, pos); } static void drag_leave_hover(struct x11_display *d, struct x11_surface *s, struct yasl_vector2i pos) { if (!d->drag.active || d->drag.hover != s) return; d->drag.hover = NULL; - if (s && !s->destroyed && s->cbs && s->cbs->drag) - s->cbs->drag(&s->base, YASL_DRAG_LEAVE, pos, s->ud); + if (s && !s->destroyed) + yasl_surface_notify_drag(&s->base, YASL_DRAG_LEAVE, pos); } static void drag_drop_hover(struct x11_display *d) @@ -559,19 +567,41 @@ static void cancel_pastes(struct x11_display *d, struct x11_surface *target) { for (struct paste_request **p = &d->pastes; *p;) { struct paste_request *r = *p; - if (!target) { + if (target && r->target == target) { + /* Keep the requestor window alive until the selection owner replies. + * Some INCR owners write to it after the original surface is gone. */ + r->target = NULL; + r->id = 0; + p = &r->next; + } else if (!target) { *p = r->next; + if (r->timeout) yasl_loop_del_timer(d->loop, r->timeout); XDestroyWindow(d->native, r->window); free(r); } else { - /* Keep the requestor alive until SelectionNotify: the owner may - * still be writing to its property. Never retain a dead target. */ - if (r->target == target) r->target = NULL; p = &r->next; } } } +static uint64_t paste_timeout(void *ud, yasl_timer_id id, uint64_t interval_ns) +{ + (void)id; + (void)interval_ns; + struct paste_request *r = ud; + struct x11_surface *s = r->target; + struct x11_display *d = r->display; + struct paste_request **p = &d->pastes; + while (*p && *p != r) p = &(*p)->next; + if (*p) *p = r->next; + r->timeout = 0; /* The loop retires this one-shot timer after the callback. */ + XDestroyWindow(d->native, r->window); + if (s && !s->destroyed) + yasl_surface_complete_paste(&s->base, r->id, YASL_TRANSFER_FAILED, NULL, 0); + free(r); + return 0; +} + static void surface_destroy(struct yasl_surface *base) { struct x11_surface *s = (struct x11_surface *)base; @@ -628,6 +658,7 @@ static void set_callbacks(struct yasl_surface *base, if (s->destroyed) return; s->base.callbacks = cbs ? *cbs : (struct yasl_window_cbs){0}; s->cbs = &s->base.callbacks; + s->base.ud = ud; s->ud = ud; } @@ -641,7 +672,7 @@ static void set_title(struct yasl_surface *base, const char *title) PropModeReplace, (const unsigned char *)title, (int)strlen(title)); } -static void net_message(struct x11_surface *s, Atom type, +static bool net_message(struct x11_surface *s, Atom type, long a, long b, long c, long e, long f) { struct x11_display *d = (struct x11_display *)s->base.display; @@ -650,8 +681,246 @@ static void net_message(struct x11_surface *s, Atom type, .type = ClientMessage, .display = d->native, .window = s->window, .message_type = type, .format = 32, .data.l = {a, b, c, e, f}, }; - XSendEvent(d->native, RootWindow(d->native, d->screen), False, - SubstructureRedirectMask | SubstructureNotifyMask, &event); + return XSendEvent(d->native, RootWindow(d->native, d->screen), False, + SubstructureRedirectMask | SubstructureNotifyMask, &event) != 0; +} + +static bool surface_set_size(struct yasl_surface *base, struct yasl_vector2i size) +{ + struct x11_surface *s = (struct x11_surface *)base; + struct x11_display *d = (struct x11_display *)base->display; + if (s->destroyed || s->is_popup || size.x <= 0 || size.y <= 0 || + size.x > 32767 || size.y > 32767) { + errno = EINVAL; + return false; + } + if (!d->native || !s->window) { errno = ENODEV; return false; } + XResizeWindow(d->native, s->window, (unsigned)size.x, (unsigned)size.y); + XFlush(d->native); + return true; +} + +static bool surface_set_fullscreen(struct yasl_surface *base, bool enabled) +{ + struct x11_surface *s = (struct x11_surface *)base; + struct x11_display *d = (struct x11_display *)base->display; + if (s->destroyed || s->is_popup) { errno = EINVAL; return false; } + if (!d->native || !s->window) { errno = ENODEV; return false; } + if (!net_message(s, d->net_state, enabled ? 1 : 0, d->fullscreen, 0, 1, 0)) { + errno = ENOTSUP; + return false; + } + XFlush(d->native); + return true; +} + +static bool surface_set_maximized(struct yasl_surface *base, bool enabled) +{ + struct x11_surface *s = (struct x11_surface *)base; + struct x11_display *d = (struct x11_display *)base->display; + if (s->destroyed || s->is_popup) { errno = EINVAL; return false; } + if (!d->native || !s->window) { errno = ENODEV; return false; } + long action = enabled ? 1 : 0; + if (!net_message(s, d->net_state, action, d->maximized_v, d->maximized_h, 1, 0)) { + errno = ENOTSUP; + return false; + } + XFlush(d->native); + return true; +} + +static bool surface_minimize(struct yasl_surface *base) +{ + struct x11_surface *s = (struct x11_surface *)base; + struct x11_display *d = (struct x11_display *)base->display; + if (s->destroyed || s->is_popup) { errno = EINVAL; return false; } + if (!d->native || !s->window) { errno = ENODEV; return false; } + if (!XIconifyWindow(d->native, s->window, d->screen)) { errno = ENOTSUP; return false; } + XFlush(d->native); + return true; +} + +static size_t utf8_offset_for_chars(const char *text, size_t len, int chars) +{ + size_t at = 0; + while (at < len && chars-- > 0) { + unsigned char c = (unsigned char)text[at]; + size_t step = c < 0x80 ? 1 : (c < 0xe0 ? 2 : (c < 0xf0 ? 3 : 4)); + if (step > len - at) step = 1; + at += step; + } + return at; +} + +static size_t utf8_encode(char *out, uint32_t cp) +{ + if (!cp || cp > 0x10ffff || (cp >= 0xd800 && cp <= 0xdfff)) cp = 0xfffd; + if (cp < 0x80) { out[0] = (char)cp; return 1; } + if (cp < 0x800) { + out[0] = (char)(0xc0 | (cp >> 6)); + out[1] = (char)(0x80 | (cp & 0x3f)); + return 2; + } + if (cp < 0x10000) { + out[0] = (char)(0xe0 | (cp >> 12)); + out[1] = (char)(0x80 | ((cp >> 6) & 0x3f)); + out[2] = (char)(0x80 | (cp & 0x3f)); + return 3; + } + out[0] = (char)(0xf0 | (cp >> 18)); + out[1] = (char)(0x80 | ((cp >> 12) & 0x3f)); + out[2] = (char)(0x80 | ((cp >> 6) & 0x3f)); + out[3] = (char)(0x80 | (cp & 0x3f)); + return 4; +} + +static char *xim_text_dup(const XIMText *text, size_t *len) +{ + *len = 0; + if (!text) return NULL; + const void *source = text->encoding_is_wchar + ? (const void *)text->string.wide_char + : (const void *)text->string.multi_byte; + if (!source) return NULL; + /* XIMText.length is an unsigned short, so four bytes per scalar cannot + * overflow size_t on the supported X11 targets. */ + size_t cap = (size_t)text->length * 4 + 1; + char *out = malloc(cap); + if (!out) return NULL; + size_t used = 0; + if (text->encoding_is_wchar) { + const wchar_t *src = text->string.wide_char; + for (unsigned i = 0; i < text->length; i++) { + uint32_t cp = (uint32_t)src[i]; + used += utf8_encode(out + used, cp); + } + } else { + const char *src = text->string.multi_byte; + mbstate_t state = {0}; + size_t consumed_bytes = 0; + size_t max_bytes = (size_t)text->length * MB_LEN_MAX; + unsigned converted = 0; + while (converted < text->length && consumed_bytes < max_bytes) { + wchar_t wc = 0; + size_t result = mbrtowc(&wc, src + consumed_bytes, 1, &state); + if (result == (size_t)-1) { free(out); return NULL; } + consumed_bytes++; + if (result == (size_t)-2) continue; + if (result == 0) { + /* The public UTF-8 event payload cannot contain an embedded NUL. */ + wc = (wchar_t)0xfffd; + state = (mbstate_t){0}; + } + used += utf8_encode(out + used, (uint32_t)wc); + converted++; + } + if (converted != text->length) { free(out); return NULL; } + } + out[used] = '\0'; + if (!yasl_utf8_validate(out, used)) { free(out); return NULL; } + *len = used; + return out; +} + +static void xim_preedit_done(XIM im, XPointer client_data, XPointer call_data) +{ + (void)im; + (void)call_data; + struct x11_surface *s = (struct x11_surface *)client_data; + if (s->destroyed) return; + free(s->ime_preedit); + s->ime_preedit = NULL; + s->ime_cursor_begin = s->ime_cursor_end = -1; + yasl_surface_notify_composition(&s->base, NULL, -1, -1); +} + +static int xim_preedit_start(XIM im, XPointer client_data, XPointer call_data) +{ + (void)im; + (void)client_data; + (void)call_data; + return -1; /* Accept preedit strings of any length. */ +} + +static void xim_preedit_draw(XIM im, XPointer client_data, XPointer call_data) +{ + (void)im; + struct x11_surface *s = (struct x11_surface *)client_data; + XIMPreeditDrawCallbackStruct *draw = (XIMPreeditDrawCallbackStruct *)call_data; + if (s->destroyed || !draw || draw->chg_first < 0 || draw->chg_length < 0) return; + size_t old_len = s->ime_preedit ? strlen(s->ime_preedit) : 0; + size_t new_len = old_len; + bool has_string = draw->text && (draw->text->encoding_is_wchar + ? draw->text->string.wide_char != NULL + : draw->text->string.multi_byte != NULL); + if (has_string) { + const char *old_text = s->ime_preedit ? s->ime_preedit : ""; + size_t first = utf8_offset_for_chars(old_text, old_len, draw->chg_first); + size_t after = first + utf8_offset_for_chars(old_text + first, + old_len - first, draw->chg_length); + size_t insert_len = 0; + char *insert = xim_text_dup(draw->text, &insert_len); + if (!insert) return; + if (insert_len > SIZE_MAX - (old_len - (after - first)) - 1) { + free(insert); + return; + } + new_len = old_len - (after - first) + insert_len; + char *updated = malloc(new_len + 1); + if (!updated) { free(insert); return; } + if (first) memcpy(updated, old_text, first); + if (insert_len) memcpy(updated + first, insert, insert_len); + if (old_len > after) memcpy(updated + first + insert_len, old_text + after, + old_len - after); + updated[new_len] = '\0'; + free(insert); + free(s->ime_preedit); + s->ime_preedit = new_len ? updated : (free(updated), NULL); + } + int caret = draw->caret < 0 ? -1 : (int)utf8_offset_for_chars( + s->ime_preedit ? s->ime_preedit : "", new_len, draw->caret); + s->ime_cursor_begin = s->ime_cursor_end = caret; + yasl_surface_notify_composition(&s->base, s->ime_preedit, caret, caret); +} + +static void xim_preedit_caret(XIM im, XPointer client_data, XPointer call_data) +{ + (void)im; + struct x11_surface *s = (struct x11_surface *)client_data; + XIMPreeditCaretCallbackStruct *caret = (XIMPreeditCaretCallbackStruct *)call_data; + if (s->destroyed || !caret || !s->ime_preedit) return; + int offset = caret->position < 0 ? -1 : (int)utf8_offset_for_chars( + s->ime_preedit, strlen(s->ime_preedit), caret->position); + s->ime_cursor_begin = s->ime_cursor_end = offset; + yasl_surface_notify_composition(&s->base, s->ime_preedit, offset, offset); +} + +static XIC create_ic(struct x11_display *d, struct x11_surface *s) +{ + if (!d->im) return NULL; + union { + int (*start)(XIM, XPointer, XPointer); + XIMProc proc; + } start_proc = {.start = xim_preedit_start}; + XIMCallback start = {.client_data = (XPointer)s, + .callback = start_proc.proc}; + XIMCallback done = {.client_data = (XPointer)s, .callback = xim_preedit_done}; + XIMCallback draw = {.client_data = (XPointer)s, .callback = xim_preedit_draw}; + XIMCallback caret = {.client_data = (XPointer)s, .callback = xim_preedit_caret}; + XVaNestedList preedit = XVaCreateNestedList(0, + XNPreeditStartCallback, &start, + XNPreeditDoneCallback, &done, + XNPreeditDrawCallback, &draw, + XNPreeditCaretCallback, &caret, NULL); + XIC ic = preedit ? XCreateIC(d->im, + XNInputStyle, XIMPreeditCallbacks | XIMStatusNothing, + XNPreeditAttributes, preedit, + XNClientWindow, s->window, XNFocusWindow, s->window, NULL) : NULL; + if (preedit) XFree(preedit); + if (!ic) + ic = XCreateIC(d->im, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, + XNClientWindow, s->window, XNFocusWindow, s->window, NULL); + return ic; } static struct yasl_surface *create_toplevel(struct yasl_display *base, @@ -691,10 +960,7 @@ static struct yasl_surface *create_toplevel(struct yasl_display *base, Atom xdnd_version = 5; XChangeProperty(d->native, s->window, d->xdnd_aware, XA_ATOM, 32, PropModeReplace, (unsigned char *)&xdnd_version, 1); - if (d->im) { - s->ic = XCreateIC(d->im, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, - XNClientWindow, s->window, XNFocusWindow, s->window, NULL); - } + s->ic = create_ic(d, s); s->next = d->surfaces; d->surfaces = s; set_title(&s->base, spec->title ? spec->title : "YASL"); @@ -851,10 +1117,7 @@ static struct yasl_surface *surface_create_popup(struct yasl_surface *parent_bas Atom xdnd_version = 5; XChangeProperty(d->native, s->window, d->xdnd_aware, XA_ATOM, 32, PropModeReplace, (unsigned char *)&xdnd_version, 1); - if (d->im) { - s->ic = XCreateIC(d->im, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, - XNClientWindow, s->window, XNFocusWindow, s->window, NULL); - } + s->ic = create_ic(d, s); XMapRaised(d->native, s->window); XFlush(d->native); } @@ -909,7 +1172,7 @@ static void update_state(struct x11_surface *s) if (data) XFree(data); if (state == s->base.state) return; s->base.state = state; - if (s->cbs && s->cbs->state) s->cbs->state(&s->base, state, s->ud); + yasl_surface_notify_state(&s->base, state); } static void selection_request(struct x11_display *d, XSelectionRequestEvent *request) @@ -1036,13 +1299,48 @@ static void selection_notify(struct x11_display *d, XSelectionEvent *event) while (*p && (*p)->window != event->requestor) p = &(*p)->next; if (!*p) return; struct paste_request *r = *p; - *p = r->next; struct x11_surface *s = r->target; - if (!s || event->property == None) { + if (!s) { + /* Surface destruction detached this accepted request. The reply means + * the owner has finished writing the initial property; retire safely. */ + *p = r->next; + if (r->timeout) yasl_loop_del_timer(d->loop, r->timeout); XDestroyWindow(d->native, r->window); free(r); return; } + if (event->property == None) { + *p = r->next; + if (r->timeout) yasl_loop_del_timer(d->loop, r->timeout); + enum yasl_transfer_status status = + XGetSelectionOwner(d->native, r->selection) == None + ? YASL_TRANSFER_UNAVAILABLE : YASL_TRANSFER_FAILED; + XDestroyWindow(d->native, r->window); + if (s && !s->destroyed) + yasl_surface_complete_paste(&s->base, r->id, status, NULL, 0); + free(r); + return; + } + /* Probe without deleting first. Deleting an INCR marker acknowledges it + * and invites the owner to write more properties; this backend rejects + * streaming, so leave it intact and retain a tombstone until timeout. */ + Atom probe_type = None; + int probe_format = 0; + unsigned long probe_count = 0, probe_left = 0; + unsigned char *probe_data = NULL; + int probe_result = XGetWindowProperty(d->native, r->window, event->property, + 0, 0, False, AnyPropertyType, &probe_type, &probe_format, + &probe_count, &probe_left, &probe_data); + if (probe_data) XFree(probe_data); + if (probe_result == Success && probe_type == d->incr) { + yasl_surface_complete_paste(&s->base, r->id, + YASL_TRANSFER_FAILED, NULL, 0); + r->target = NULL; + r->id = 0; + return; + } + *p = r->next; + if (r->timeout) yasl_loop_del_timer(d->loop, r->timeout); Atom actual = None; int format = 0; unsigned long count = 0, left = 0; @@ -1070,7 +1368,12 @@ static void selection_notify(struct x11_display *d, XSelectionEvent *event) if (data) { XFree(data); data = NULL; } break; } - char *nb = realloc(buf, total + count + 1); + if (count > TEXT_LIMIT - total) { + XFree(data); + data = NULL; + break; + } + char *nb = realloc(buf, total + (size_t)count + 1); if (!nb) { XFree(data); break; @@ -1079,7 +1382,7 @@ static void selection_notify(struct x11_display *d, XSelectionEvent *event) if (count) memcpy(buf + total, data, count); total += count; buf[total] = '\0'; - offset += count / 4; + offset += (count + 3) / 4; XFree(data); data = NULL; if (left == 0) { complete = true; break; } @@ -1091,8 +1394,12 @@ static void selection_notify(struct x11_display *d, XSelectionEvent *event) buf = utf8; if (buf) total = utf8_len; } + enum yasl_transfer_status status = YASL_TRANSFER_FAILED; if (complete && buf && yasl_utf8_validate(buf, total)) - push(s, (struct yasl_event){.type = YASL_EVENT_PASTE}, buf, total); + status = YASL_TRANSFER_OK; + yasl_surface_complete_paste(&s->base, r->id, status, + status == YASL_TRANSFER_OK ? buf : NULL, + status == YASL_TRANSFER_OK ? total : 0); free(buf); XDestroyWindow(d->native, r->window); free(r); @@ -1172,8 +1479,8 @@ static void handle_client_message(struct x11_display *d, struct x11_surface *s, const XClientMessageEvent *ev) { if (ev->message_type == d->protocols && - (Atom)ev->data.l[0] == d->delete_window && s->cbs && s->cbs->close) { - s->cbs->close(&s->base, s->ud); + (Atom)ev->data.l[0] == d->delete_window) { + yasl_surface_notify_close(&s->base); } else if (ev->message_type == d->xdnd_enter) { Window source = (Window)ev->data.l[0]; int version = (int)((unsigned long)ev->data.l[1] >> 24); @@ -1204,25 +1511,29 @@ static void handle_client_message(struct x11_display *d, struct x11_surface *s, d->xdnd.accepts = has_uri; } else if (ev->message_type == d->xdnd_position) { Window source = (Window)ev->data.l[0]; + Window target = s->window; int rx = (int)(int16_t)((ev->data.l[2] >> 16) & 0xFFFF); int ry = (int)(int16_t)(ev->data.l[2] & 0xFFFF); Time t = (Time)ev->data.l[3]; int wx = rx, wy = ry; if (d->native) { Window child; - XTranslateCoordinates(d->native, RootWindow(d->native, d->screen), s->window, + XTranslateCoordinates(d->native, RootWindow(d->native, d->screen), target, rx, ry, &wx, &wy, &child); } struct yasl_vector2i pos = {wx, wy}; if (d->xdnd.surface && d->xdnd.surface != s) { - if (d->xdnd.accepts && d->xdnd.surface->cbs && d->xdnd.surface->cbs->drop) - d->xdnd.surface->cbs->drop(&d->xdnd.surface->base, YASL_DROP_LEAVE, d->xdnd.pos, d->xdnd.surface->ud); + struct x11_surface *old = d->xdnd.surface; + struct yasl_vector2i old_pos = d->xdnd.pos; + d->xdnd.surface = NULL; + if (d->xdnd.accepts && !old->destroyed) + yasl_surface_notify_drop(&old->base, YASL_DROP_LEAVE, old_pos); } d->xdnd.surface = s; d->xdnd.pos = pos; d->xdnd.time = t; - if (d->xdnd.accepts && s->cbs && s->cbs->drop) - s->cbs->drop(&s->base, YASL_DROP_OVER, pos, s->ud); + if (d->xdnd.accepts && !s->destroyed) + yasl_surface_notify_drop(&s->base, YASL_DROP_OVER, pos); if (d->native) { XEvent reply = {0}; @@ -1233,37 +1544,38 @@ static void handle_client_message(struct x11_display *d, struct x11_surface *s, .message_type = d->xdnd_status, .format = 32, .data.l = { - s->window, - d->xdnd.accepts ? 1 : 0, + target, + d->xdnd.accepts && !s->destroyed ? 1 : 0, 0, 0, - d->xdnd.accepts ? d->xdnd_action_copy : None, + d->xdnd.accepts && !s->destroyed ? d->xdnd_action_copy : None, }, }; XSendEvent(d->native, source, False, NoEventMask, &reply); } } else if (ev->message_type == d->xdnd_leave) { if (d->xdnd.surface == s) { - if (d->xdnd.accepts && s->cbs && s->cbs->drop) - s->cbs->drop(&s->base, YASL_DROP_LEAVE, d->xdnd.pos, s->ud); + if (d->xdnd.accepts && !s->destroyed) + yasl_surface_notify_drop(&s->base, YASL_DROP_LEAVE, d->xdnd.pos); } d->xdnd = (typeof(d->xdnd)){0}; } else if (ev->message_type == d->xdnd_drop) { Window source = (Window)ev->data.l[0]; + Window target = s->window; Time drop_time = (Time)ev->data.l[2]; bool was_accepting = d->xdnd.accepts && d->xdnd.has_uri_list && (d->xdnd.source == source); if (d->xdnd.surface == s) { - if (was_accepting && s->cbs && s->cbs->drop) - s->cbs->drop(&s->base, YASL_DROP_LEAVE, d->xdnd.pos, s->ud); + if (was_accepting && !s->destroyed) + yasl_surface_notify_drop(&s->base, YASL_DROP_LEAVE, d->xdnd.pos); } - if (was_accepting) { + if (was_accepting && !s->destroyed) { d->pending_drop.active = true; d->pending_drop.source = source; d->pending_drop.surface = s; d->pending_drop.time = drop_time; if (d->native) { XConvertSelection(d->native, d->xdnd_selection, d->uri_list, - d->paste_property, s->window, drop_time); + d->paste_property, target, drop_time); } } else if (d->native) { XEvent reply = {0}; @@ -1273,7 +1585,7 @@ static void handle_client_message(struct x11_display *d, struct x11_surface *s, .window = source, .message_type = d->xdnd_finished, .format = 32, - .data.l = { s->window, 0, None }, + .data.l = { target, 0, None }, }; XSendEvent(d->native, source, False, NoEventMask, &reply); } @@ -1326,8 +1638,9 @@ static void dispatch(struct yasl_display *base) case ConfigureNotify: { struct yasl_vector2i size = {event.xconfigure.width, event.xconfigure.height}; if (size.x == s->size.x && size.y == s->size.y) break; - if (resize(s, size) && s->cbs && s->cbs->configure) - s->cbs->configure(&s->base, (struct yasl_geometry){size, YASL_SCALE_UNIT}, s->ud); + if (resize(s, size)) + yasl_surface_notify_configure(&s->base, + (struct yasl_geometry){size, YASL_SCALE_UNIT}); break; } case ClientMessage: @@ -1340,7 +1653,7 @@ static void dispatch(struct yasl_display *base) else s->base.state &= ~YASL_STATE_ACTIVATED; push(s, (struct yasl_event){ .type = YASL_EVENT_FOCUS, .focus.focused = focused}, NULL, 0); - if (s->cbs && s->cbs->state) s->cbs->state(&s->base, s->base.state, s->ud); + yasl_surface_notify_state(&s->base, s->base.state); break; } case PropertyNotify: @@ -1514,8 +1827,8 @@ static void flush(struct yasl_display *base) .target = {.generation = yasl_frame_begin(&s->base), .pixels = s->pixels, .size = s->size}, }; - s->cbs->draw(&s->base, &s->frame, s->ud); - s->drawing = false; + yasl_surface_draw(&s->base, &s->frame); + if (!s->destroyed) s->drawing = false; } XFlush(d->native); leave(d); @@ -1525,7 +1838,13 @@ static int ready(int fd, short events, void *ud) { (void)fd; struct yasl_display *base = ud; - if (events & (POLLIN | POLLERR | POLLHUP)) { + struct x11_display *d = (struct x11_display *)base; + if ((events & (POLLERR | POLLHUP)) && !d->lost) { + d->lost = true; + yasl_display_notify_event(base, YASL_EVENT_DISPLAY_LOST); + return 0; + } + if ((events & POLLIN) && !d->lost) { dispatch(base); flush(base); } @@ -1535,7 +1854,7 @@ static int ready(int fd, short events, void *ud) static int dispatch_timeout(struct yasl_display *base, int timeout_ms) { struct x11_display *d = (struct x11_display *)base; - if (unlikely(!d || !d->native)) return -1; + if (unlikely(!d || !d->native || d->lost)) return -1; dispatch(base); for (struct x11_surface *s = d->surfaces; s; s = s->next) { if (s->want_draw || s->input.count || s->input.reset_pending) { @@ -1692,46 +2011,96 @@ static uint32_t caps(const struct yasl_display *d) (((const struct x11_display *)d)->xi2 ? YASL_CAP_TABLET : 0); } -static void copy(struct yasl_display *base, enum yasl_selection sel, const char *text) +static bool copy_text(struct yasl_display *base, enum yasl_selection sel, const char *text) { - if (!text || (sel != YASL_CLIPBOARD && sel != YASL_PRIMARY)) return; + if (!text || (sel != YASL_CLIPBOARD && sel != YASL_PRIMARY)) { + errno = EINVAL; + return false; + } struct x11_display *d = (struct x11_display *)base; char *value = strdup(text); - if (!value) return; + if (!value) { errno = ENOMEM; return false; } free(d->selection[sel]); d->selection[sel] = value; XSetSelectionOwner(d->native, sel == YASL_PRIMARY ? XA_PRIMARY : d->clipboard, d->selection_window, CurrentTime); XFlush(d->native); + return true; } -static void paste(struct yasl_display *base, enum yasl_selection sel, struct yasl_surface *target) +static void copy(struct yasl_display *base, enum yasl_selection sel, const char *text) { - if (sel != YASL_CLIPBOARD && sel != YASL_PRIMARY) return; + (void)copy_text(base, sel, text); +} + +static bool request_paste(struct yasl_display *base, enum yasl_selection sel, + struct yasl_surface *target, uint64_t id) +{ + if (!target || (sel != YASL_CLIPBOARD && sel != YASL_PRIMARY)) { + errno = EINVAL; + return false; + } struct x11_display *d = (struct x11_display *)base; struct x11_surface *s = (struct x11_surface *)target; + if (s->destroyed || target->display != base) { errno = EINVAL; return false; } Atom selection = sel == YASL_PRIMARY ? XA_PRIMARY : d->clipboard; - if (XGetSelectionOwner(d->native, selection) == d->selection_window) { + Window owner = XGetSelectionOwner(d->native, selection); + if (owner == None) { + yasl_surface_complete_paste(target, id, YASL_TRANSFER_UNAVAILABLE, NULL, 0); + return true; + } + if (owner == d->selection_window) { const char *text = d->selection[sel]; - if (text) push(s, (struct yasl_event){.type = YASL_EVENT_PASTE}, text, strlen(text)); + if (text) + yasl_surface_complete_paste(target, id, YASL_TRANSFER_OK, text, strlen(text)); + else + yasl_surface_complete_paste(target, id, YASL_TRANSFER_UNAVAILABLE, NULL, 0); + return true; } else { struct paste_request *r = calloc(1, sizeof(*r)); - if (!r) return; + if (!r) { errno = ENOMEM; return false; } r->window = XCreateSimpleWindow(d->native, RootWindow(d->native, d->screen), 0, 0, 1, 1, 0, 0, 0); + if (!r->window) { free(r); errno = EIO; return false; } + r->display = d; r->target = s; + r->selection = selection; + r->id = id; + errno = 0; + r->timeout = yasl_loop_add_timer(d->loop, PASTE_TIMEOUT_NS, + paste_timeout, r); + if (!r->timeout) { + XDestroyWindow(d->native, r->window); + free(r); + if (!errno) errno = ENOMEM; + return false; + } r->next = d->pastes; d->pastes = r; XConvertSelection(d->native, selection, d->utf8, d->paste_property, r->window, CurrentTime); XFlush(d->native); + return true; } } +static void paste(struct yasl_display *base, enum yasl_selection sel, + struct yasl_surface *target) +{ + (void)request_paste(base, sel, target, 0); +} + static bool input_poll(struct yasl_surface *base, struct yasl_event *event) { struct x11_surface *s = (struct x11_surface *)base; - return !s->destroyed && input_queue_pop(&s->input, event); + if (s->destroyed) return false; + if (base->display->events) return yasl_events_poll_surface(base, event); + return input_queue_pop(&s->input, event); +} + +static struct input_queue *surface_input_queue(struct yasl_surface *base) +{ + return &((struct x11_surface *)base)->input; } static void schedule_redraw(struct yasl_surface *base) @@ -1929,11 +2298,11 @@ static void drag_end(struct x11_display *d, enum yasl_drag_end how) d->drag.icon_pixmap = None; } - if (hover && !hover->destroyed && hover->cbs && hover->cbs->drag) { + if (hover && !hover->destroyed) { if (how == YASL_DRAG_DROPPED) - hover->cbs->drag(&hover->base, YASL_DRAG_DROP, hover_pos, hover->ud); + yasl_surface_notify_drag(&hover->base, YASL_DRAG_DROP, hover_pos); else - hover->cbs->drag(&hover->base, YASL_DRAG_LEAVE, hover_pos, hover->ud); + yasl_surface_notify_drag(&hover->base, YASL_DRAG_LEAVE, hover_pos); } if (l.end) @@ -2130,12 +2499,19 @@ const struct yasl_backend_ops yasl_x11_backend = { .display_modifiers = mods, .display_copy = copy, .display_paste = paste, + .display_copy_text = copy_text, + .display_request_paste = request_paste, .display_drag_start = drag_start, .display_drag_cancel = drag_cancel, .surface_create_toplevel = create_toplevel, .surface_create_popup = surface_create_popup, .surface_popup_reposition = surface_popup_reposition, .surface_destroy = surface_destroy, + .surface_input_queue = surface_input_queue, + .surface_set_size = surface_set_size, + .surface_set_fullscreen = surface_set_fullscreen, + .surface_set_maximized = surface_set_maximized, + .surface_minimize = surface_minimize, .surface_display = surface_display, .surface_set_callbacks = set_callbacks, .surface_set_title = set_title, |
