summaryrefslogtreecommitdiff
path: root/src/platform
diff options
context:
space:
mode:
Diffstat (limited to 'src/platform')
-rw-r--r--src/platform/cocoa/window.m275
-rw-r--r--src/platform/wayland/api.h3
-rw-r--r--src/platform/wayland/backend.c78
-rw-r--r--src/platform/wayland/buffer.c15
-rw-r--r--src/platform/wayland/clipboard.c152
-rw-r--r--src/platform/wayland/clipboard.h9
-rw-r--r--src/platform/wayland/drag.c8
-rw-r--r--src/platform/wayland/ime.c5
-rw-r--r--src/platform/wayland/keyboard.c54
-rw-r--r--src/platform/wayland/pointer.c10
-rw-r--r--src/platform/wayland/shell.c7
-rw-r--r--src/platform/wayland/surface.c7
-rw-r--r--src/platform/x11/window.c504
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,