diff options
Diffstat (limited to 'src/platform')
| -rw-r--r-- | src/platform/cocoa/desktop.m | 145 | ||||
| -rw-r--r-- | src/platform/cocoa/meson.build | 2 | ||||
| -rw-r--r-- | src/platform/cocoa/window.m | 10 | ||||
| -rw-r--r-- | src/platform/freedesktop/dbus.c | 187 | ||||
| -rw-r--r-- | src/platform/freedesktop/dbus.h | 16 | ||||
| -rw-r--r-- | src/platform/freedesktop/desktop.c | 150 | ||||
| -rw-r--r-- | src/platform/freedesktop/meson.build | 1 | ||||
| -rw-r--r-- | src/platform/meson.build | 4 | ||||
| -rw-r--r-- | src/platform/wayland/api.h | 1 | ||||
| -rw-r--r-- | src/platform/wayland/backend.c | 6 | ||||
| -rw-r--r-- | src/platform/wayland/internal.h | 2 | ||||
| -rw-r--r-- | src/platform/wayland/shell.c | 9 | ||||
| -rw-r--r-- | src/platform/wayland/surface.c | 2 | ||||
| -rw-r--r-- | src/platform/x11/window.c | 14 |
14 files changed, 548 insertions, 1 deletions
diff --git a/src/platform/cocoa/desktop.m b/src/platform/cocoa/desktop.m new file mode 100644 index 0000000..526cbb9 --- /dev/null +++ b/src/platform/cocoa/desktop.m @@ -0,0 +1,145 @@ +#import <AppKit/AppKit.h> +#import <UserNotifications/UserNotifications.h> + +#include "common/desktop.h" +#include "yasl/desktop.h" + +#include <CoreFoundation/CoreFoundation.h> +#include <limits.h> +#include <mach-o/dyld.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +static size_t copy_str(char *buf, size_t cap, const char *s) +{ + size_t len = s ? strlen(s) : 0; + if (!len || len >= cap) return 0; + memcpy(buf, s, len + 1); + return len; +} + +bool desktop_open_uri(const char *uri) +{ + @autoreleasepool { + NSString *str = [NSString stringWithUTF8String:uri]; + NSURL *url = str ? [NSURL URLWithString:str] : nil; + return url && [[NSWorkspace sharedWorkspace] openURL:url]; + } +} + +/* Show banners while the application is in front, too. */ +@interface YaslNotificationDelegate : NSObject <UNUserNotificationCenterDelegate> +@end + +@implementation YaslNotificationDelegate +- (void)userNotificationCenter:(UNUserNotificationCenter *)center + willPresentNotification:(UNNotification *)notification + withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler +{ + (void)center; + (void)notification; + completionHandler(UNNotificationPresentationOptionBanner); +} +@end + +static YaslNotificationDelegate *notification_delegate; +static bool authorization_requested; + +/* UNUserNotificationCenter raises an exception outside an app bundle. */ +static bool notifications_available(void) +{ + NSBundle *bundle = [NSBundle mainBundle]; + return bundle.bundleIdentifier && [bundle.bundlePath.pathExtension isEqualToString:@"app"]; +} + +bool desktop_notify(const char *app_id, const char *title, const char *body) +{ + (void)app_id; + if (!notifications_available()) return false; + @autoreleasepool { + NSString *heading = [NSString stringWithUTF8String:title]; + NSString *message = [NSString stringWithUTF8String:body]; + if (!heading || !message) return false; + UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; + if (!notification_delegate) { + notification_delegate = [[YaslNotificationDelegate alloc] init]; + center.delegate = notification_delegate; + } + if (!authorization_requested) { + authorization_requested = true; + [center requestAuthorizationWithOptions:UNAuthorizationOptionAlert + completionHandler:^(BOOL granted, NSError *error) { + (void)granted; + (void)error; + }]; + } + UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init]; + content.title = heading; + content.body = message; + UNNotificationRequest *request = + [UNNotificationRequest requestWithIdentifier:[[NSUUID UUID] UUIDString] + content:content + trigger:nil]; + [content release]; + [center addNotificationRequest:request withCompletionHandler:nil]; + return true; + } +} + +uint32_t desktop_appearance(void) +{ + @autoreleasepool { + uint32_t flags = 0; + NSAppearanceName name = [NSApp.effectiveAppearance + bestMatchFromAppearancesWithNames:@[NSAppearanceNameAqua, NSAppearanceNameDarkAqua]]; + if ([name isEqualToString:NSAppearanceNameDarkAqua]) + flags |= YASL_APPEARANCE_DARK; + else if (name) + flags |= YASL_APPEARANCE_LIGHT; + if ([NSWorkspace sharedWorkspace].accessibilityDisplayShouldIncreaseContrast) + flags |= YASL_APPEARANCE_HIGH_CONTRAST; + return flags; + } +} + +size_t desktop_executable_path(char *buf, size_t cap) +{ + char raw[PATH_MAX]; + uint32_t size = sizeof(raw); + char resolved[PATH_MAX]; + if (_NSGetExecutablePath(raw, &size) != 0 || !realpath(raw, resolved)) return 0; + return copy_str(buf, cap, resolved); +} + +size_t desktop_resource_dir(char *buf, size_t cap) +{ + @autoreleasepool { + NSBundle *bundle = [NSBundle mainBundle]; + if (!bundle.bundleIdentifier || ![bundle.bundlePath.pathExtension isEqualToString:@"app"]) + return 0; + NSString *path = bundle.resourcePath; + return path ? copy_str(buf, cap, path.fileSystemRepresentation) : 0; + } +} + +size_t desktop_default_locale(char *buf, size_t cap) +{ + /* Identifiers such as "zh-Hans_CN" are not POSIX names, so build the name + * from the language and region codes alone. */ + char name[64] = "en_US.UTF-8"; + CFLocaleRef locale = CFLocaleCopyCurrent(); + if (locale) { + CFStringRef language = CFLocaleGetValue(locale, kCFLocaleLanguageCode); + CFStringRef region = CFLocaleGetValue(locale, kCFLocaleCountryCode); + char lang[16], reg[16]; + if (language && CFStringGetCString(language, lang, sizeof(lang), kCFStringEncodingUTF8) && lang[0]) { + if (region && CFStringGetCString(region, reg, sizeof(reg), kCFStringEncodingUTF8) && reg[0]) + snprintf(name, sizeof(name), "%s_%s.UTF-8", lang, reg); + else + snprintf(name, sizeof(name), "%s.UTF-8", lang); + } + CFRelease(locale); + } + return copy_str(buf, cap, name); +} diff --git a/src/platform/cocoa/meson.build b/src/platform/cocoa/meson.build index 8230376..5fc5f5e 100644 --- a/src/platform/cocoa/meson.build +++ b/src/platform/cocoa/meson.build @@ -1 +1 @@ -sources += files('window.m', 'keymap.c') +sources += files('window.m', 'desktop.m', 'keymap.c') diff --git a/src/platform/cocoa/window.m b/src/platform/cocoa/window.m index 811a7c6..75dce54 100644 --- a/src/platform/cocoa/window.m +++ b/src/platform/cocoa/window.m @@ -1366,6 +1366,7 @@ struct cocoa_display *cocoa_display_open(struct yasl_loop *loop, const struct ya darwin_ctx.cbs = &darwin_ctx.base.callbacks; darwin_ctx.ud = spec->ud; if (spec->app_id) darwin_ctx.app_id = strdup(spec->app_id); + darwin_ctx.base.app_id = darwin_ctx.app_id; } return &darwin_ctx; } @@ -2191,6 +2192,14 @@ static bool impl_surface_place_under_pointer(struct yasl_surface *s, struct yasl return cocoa_surface_place_under_pointer((struct cocoa_surface *)s, at); } +static bool impl_surface_bell(struct yasl_surface *s) +{ + assert([NSThread isMainThread]); + (void)s; + NSBeep(); + return true; +} + static void *impl_display_get_native_handle(struct yasl_display *d) { return d ? (void *)NSApp : NULL; @@ -2249,6 +2258,7 @@ const struct yasl_backend_ops yasl_cocoa_backend = { .surface_show_window_menu = impl_surface_show_window_menu, .surface_drag_carry = impl_surface_drag_carry, .surface_place_under_pointer = impl_surface_place_under_pointer, + .surface_bell = impl_surface_bell, .display_get_native_handle = impl_display_get_native_handle, .surface_get_native_handle = impl_surface_get_native_handle, .surface_get_native_frame = impl_surface_get_native_frame, diff --git a/src/platform/freedesktop/dbus.c b/src/platform/freedesktop/dbus.c new file mode 100644 index 0000000..abca900 --- /dev/null +++ b/src/platform/freedesktop/dbus.c @@ -0,0 +1,187 @@ +#define _GNU_SOURCE +#include "platform/freedesktop/dbus.h" + +#if YASL_HAS_DBUS + +#include <dbus/dbus.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> + +/* The portal may need activation on first use; bound how long a query blocks. */ +#define PORTAL_TIMEOUT_MS 1000 + +static DBusConnection *bus; + +/* The session bus address, without libdbus's X11 autolaunch fallback. */ +static char *session_address(void) +{ + const char *env = getenv("DBUS_SESSION_BUS_ADDRESS"); + if (env && *env) return strdup(env); + const char *run = getenv("XDG_RUNTIME_DIR"); + if (!run || !*run) return NULL; + char path[4096]; + struct stat st; + if (snprintf(path, sizeof(path), "%s/bus", run) >= (int)sizeof(path) || + stat(path, &st) != 0 || !S_ISSOCK(st.st_mode)) + return NULL; + char *escaped = dbus_address_escape_value(path); + if (!escaped) return NULL; + char *address = NULL; + if (asprintf(&address, "unix:path=%s", escaped) < 0) address = NULL; + dbus_free(escaped); + return address; +} + +static DBusConnection *session_bus(void) +{ + if (bus && dbus_connection_get_is_connected(bus)) return bus; + if (bus) { + dbus_connection_close(bus); + dbus_connection_unref(bus); + bus = NULL; + } + char *address = session_address(); + if (!address) return NULL; + DBusError err; + dbus_error_init(&err); + DBusConnection *c = dbus_connection_open_private(address, &err); + free(address); + if (!c) { + dbus_error_free(&err); + return NULL; + } + dbus_connection_set_exit_on_disconnect(c, FALSE); + if (!dbus_bus_register(c, &err)) { + dbus_error_free(&err); + dbus_connection_close(c); + dbus_connection_unref(c); + return NULL; + } + bus = c; + return bus; +} + +bool bus_notify(const char *app, const char *title, const char *body) +{ + DBusConnection *c = session_bus(); + if (!c) return false; + DBusMessage *msg = dbus_message_new_method_call("org.freedesktop.Notifications", + "/org/freedesktop/Notifications", "org.freedesktop.Notifications", "Notify"); + if (!msg) return false; + + const char *icon = ""; + dbus_uint32_t replaces = 0; + dbus_int32_t expire = -1; + DBusMessageIter it, sub, entry, value; + dbus_message_iter_init_append(msg, &it); + bool ok = dbus_message_iter_append_basic(&it, DBUS_TYPE_STRING, &app) && + dbus_message_iter_append_basic(&it, DBUS_TYPE_UINT32, &replaces) && + dbus_message_iter_append_basic(&it, DBUS_TYPE_STRING, &icon) && + dbus_message_iter_append_basic(&it, DBUS_TYPE_STRING, &title) && + dbus_message_iter_append_basic(&it, DBUS_TYPE_STRING, &body); + /* No actions. */ + ok = ok && dbus_message_iter_open_container(&it, DBUS_TYPE_ARRAY, "s", &sub) && + dbus_message_iter_close_container(&it, &sub); + /* Hints: desktop-entry names the application's .desktop file. */ + const char *hint = "desktop-entry"; + ok = ok && dbus_message_iter_open_container(&it, DBUS_TYPE_ARRAY, "{sv}", &sub) && + dbus_message_iter_open_container(&sub, DBUS_TYPE_DICT_ENTRY, NULL, &entry) && + dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &hint) && + dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, "s", &value) && + dbus_message_iter_append_basic(&value, DBUS_TYPE_STRING, &app) && + dbus_message_iter_close_container(&entry, &value) && + dbus_message_iter_close_container(&sub, &entry) && + dbus_message_iter_close_container(&it, &sub); + ok = ok && dbus_message_iter_append_basic(&it, DBUS_TYPE_INT32, &expire); + + if (ok) { + dbus_message_set_no_reply(msg, TRUE); + ok = dbus_connection_send(c, msg, NULL); + if (ok) dbus_connection_flush(c); + } + dbus_message_unref(msg); + return ok; +} + +/* Read an integer from it, unwrapping variants: Settings.Read nests one more. */ +static bool iter_int(DBusMessageIter *it, int64_t *out) +{ + DBusMessageIter inner[4]; + int depth = 0; + while (dbus_message_iter_get_arg_type(it) == DBUS_TYPE_VARIANT && depth < 4) { + dbus_message_iter_recurse(it, &inner[depth]); + it = &inner[depth++]; + } + DBusBasicValue v; + switch (dbus_message_iter_get_arg_type(it)) { + case DBUS_TYPE_BYTE: dbus_message_iter_get_basic(it, &v); *out = v.byt; return true; + case DBUS_TYPE_BOOLEAN: dbus_message_iter_get_basic(it, &v); *out = v.bool_val; return true; + case DBUS_TYPE_INT16: dbus_message_iter_get_basic(it, &v); *out = v.i16; return true; + case DBUS_TYPE_UINT16: dbus_message_iter_get_basic(it, &v); *out = v.u16; return true; + case DBUS_TYPE_INT32: dbus_message_iter_get_basic(it, &v); *out = v.i32; return true; + case DBUS_TYPE_UINT32: dbus_message_iter_get_basic(it, &v); *out = v.u32; return true; + case DBUS_TYPE_INT64: dbus_message_iter_get_basic(it, &v); *out = v.i64; return true; + case DBUS_TYPE_UINT64: + dbus_message_iter_get_basic(it, &v); + if (v.u64 > INT64_MAX) return false; + *out = (int64_t)v.u64; + return true; + default: return false; + } +} + +bool portal_read_int(const char *ns, const char *key, int64_t *out) +{ + DBusConnection *c = session_bus(); + if (!c) return false; + /* ReadOne is portal version 2; older portals only have Read. */ + static const char *const methods[] = {"ReadOne", "Read"}; + for (int i = 0; i < 2; i++) { + DBusMessage *msg = dbus_message_new_method_call("org.freedesktop.portal.Desktop", + "/org/freedesktop/portal/desktop", "org.freedesktop.portal.Settings", methods[i]); + if (!msg) return false; + if (!dbus_message_append_args(msg, DBUS_TYPE_STRING, &ns, DBUS_TYPE_STRING, &key, + DBUS_TYPE_INVALID)) { + dbus_message_unref(msg); + return false; + } + DBusError err; + dbus_error_init(&err); + DBusMessage *reply = dbus_connection_send_with_reply_and_block(c, msg, + PORTAL_TIMEOUT_MS, &err); + dbus_message_unref(msg); + if (!reply) { + bool retry = dbus_error_has_name(&err, DBUS_ERROR_UNKNOWN_METHOD); + dbus_error_free(&err); + if (retry) continue; + return false; + } + DBusMessageIter it; + bool ok = dbus_message_iter_init(reply, &it) && iter_int(&it, out); + dbus_message_unref(reply); + return ok; + } + return false; +} + +#else + +bool bus_notify(const char *app, const char *title, const char *body) +{ + (void)app; + (void)title; + (void)body; + return false; +} + +bool portal_read_int(const char *ns, const char *key, int64_t *out) +{ + (void)ns; + (void)key; + (void)out; + return false; +} + +#endif diff --git a/src/platform/freedesktop/dbus.h b/src/platform/freedesktop/dbus.h new file mode 100644 index 0000000..3b1662b --- /dev/null +++ b/src/platform/freedesktop/dbus.h @@ -0,0 +1,16 @@ +#ifndef YASL_FREEDESKTOP_DBUS_H +#define YASL_FREEDESKTOP_DBUS_H + +#include <stdbool.h> +#include <stdint.h> + +/* Session-bus helpers. The first call opens a private connection that later + * calls reuse. Each returns false when YASL is built without libdbus-1 or no + * session bus is reachable. Call from one thread at a time. */ + +/* Send org.freedesktop.Notifications.Notify without waiting for a reply. */ +bool bus_notify(const char *app, const char *title, const char *body); +/* Read an integer setting through the XDG Settings portal, waiting briefly. */ +bool portal_read_int(const char *ns, const char *key, int64_t *out); + +#endif diff --git a/src/platform/freedesktop/desktop.c b/src/platform/freedesktop/desktop.c new file mode 100644 index 0000000..a8b9bc7 --- /dev/null +++ b/src/platform/freedesktop/desktop.c @@ -0,0 +1,150 @@ +#define _GNU_SOURCE +#include "common/desktop.h" +#include "platform/freedesktop/dbus.h" +#include "yasl/desktop.h" + +#include <errno.h> +#include <fcntl.h> +#include <signal.h> +#include <stdlib.h> +#include <string.h> +#include <sys/syscall.h> +#include <sys/wait.h> +#include <unistd.h> + +extern char **environ; + +static size_t copy_str(char *buf, size_t cap, const char *s) +{ + size_t len = strlen(s); + if (!len || len >= cap) return 0; + memcpy(buf, s, len + 1); + return len; +} + +/* Find an executable in PATH, like execvp but before forking. */ +static bool find_program(const char *name, char *out, size_t cap) +{ + const char *path = getenv("PATH"); + if (!path || !*path) path = "/usr/local/bin:/usr/bin:/bin"; + size_t name_len = strlen(name); + while (*path) { + const char *end = strchr(path, ':'); + size_t dir_len = end ? (size_t)(end - path) : strlen(path); + if (dir_len && dir_len + 1 + name_len < cap) { + memcpy(out, path, dir_len); + out[dir_len] = '/'; + memcpy(out + dir_len + 1, name, name_len + 1); + if (access(out, X_OK) == 0) return true; + } + if (!end) break; + path = end + 1; + } + return false; +} + +bool desktop_open_uri(const char *uri) +{ + char program[4096]; + if (!find_program("xdg-open", program, sizeof(program))) return false; + char *argv[] = {"xdg-open", (char *)uri, NULL}; + + /* Prepare everything before fork: the children of a threaded process may + * only make async-signal-safe calls. */ + struct sigaction dfl = {.sa_handler = SIG_DFL}; + sigemptyset(&dfl.sa_mask); + sigset_t none; + sigemptyset(&none); + long max_fd = sysconf(_SC_OPEN_MAX); + if (max_fd < 0 || max_fd > 65536) max_fd = 65536; + + /* The middle child exits at once, so the opener is reparented away from + * the application and never needs reaping here. */ + pid_t pid = fork(); + if (pid < 0) return false; + if (pid == 0) { + setsid(); + pid_t opener = fork(); + if (opener != 0) _exit(opener < 0 ? 1 : 0); + for (int sig = 1; sig < NSIG; sig++) + if (sig != SIGKILL && sig != SIGSTOP) sigaction(sig, &dfl, NULL); + sigprocmask(SIG_SETMASK, &none, NULL); + int null = open("/dev/null", O_RDONLY); + if (null > 0) { + dup2(null, STDIN_FILENO); + close(null); + } + /* Keep application descriptors, such as pty masters, out of the opener. */ +#ifdef SYS_close_range + if (syscall(SYS_close_range, 3u, ~0u, 0u) != 0) +#endif + for (int fd = 3; fd < max_fd; fd++) close(fd); + execve(program, argv, environ); + _exit(127); + } + + int status; + pid_t done; + do { + done = waitpid(pid, &status, 0); + } while (done < 0 && errno == EINTR); + /* An application reaper can collect the middle child first. */ + if (done < 0) return errno == ECHILD; + return WIFEXITED(status) && WEXITSTATUS(status) == 0; +} + +bool desktop_notify(const char *app_id, const char *title, const char *body) +{ + return bus_notify(app_id ? app_id : "yasl", title, body); +} + +uint32_t desktop_appearance(void) +{ + uint32_t flags = 0; + bool portal = false; + int64_t value; + if (portal_read_int("org.freedesktop.appearance", "color-scheme", &value)) { + portal = true; + if (value == 1) flags |= YASL_APPEARANCE_DARK; + else if (value == 2) flags |= YASL_APPEARANCE_LIGHT; + } + if (portal_read_int("org.freedesktop.appearance", "contrast", &value)) { + portal = true; + if (value == 1) flags |= YASL_APPEARANCE_HIGH_CONTRAST; + } + if (portal) return flags; + + /* GTK_THEME is "Name" or "Name:variant", as in "Adwaita:dark". */ + const char *theme = getenv("GTK_THEME"); + if (!theme) return 0; + if (strstr(theme, "HighContrast")) flags |= YASL_APPEARANCE_HIGH_CONTRAST; + if (strstr(theme, ":dark") || strstr(theme, "HighContrastInverse")) + flags |= YASL_APPEARANCE_DARK; + return flags; +} + +size_t desktop_executable_path(char *buf, size_t cap) +{ + /* readlink truncates silently, so a full buffer may hold a cut path. */ + ssize_t len = readlink("/proc/self/exe", buf, cap); + if (len <= 0 || (size_t)len >= cap) return 0; + buf[len] = '\0'; + return (size_t)len; +} + +size_t desktop_resource_dir(char *buf, size_t cap) +{ + (void)buf; + (void)cap; + return 0; +} + +size_t desktop_default_locale(char *buf, size_t cap) +{ + static const char *const vars[] = {"LC_ALL", "LC_CTYPE", "LANG"}; + for (size_t i = 0; i < sizeof(vars) / sizeof(vars[0]); i++) { + const char *v = getenv(vars[i]); + if (v && *v) return copy_str(buf, cap, v); + } + return copy_str(buf, cap, "C.UTF-8"); +} diff --git a/src/platform/freedesktop/meson.build b/src/platform/freedesktop/meson.build new file mode 100644 index 0000000..6f43895 --- /dev/null +++ b/src/platform/freedesktop/meson.build @@ -0,0 +1 @@ +sources += files('desktop.c', 'dbus.c') diff --git a/src/platform/meson.build b/src/platform/meson.build index 892969f..593353c 100644 --- a/src/platform/meson.build +++ b/src/platform/meson.build @@ -3,6 +3,10 @@ if system == 'linux' or system == 'darwin' subdir('posix') endif +# Desktop services shared by the Linux backends. +if system == 'linux' + subdir('freedesktop') +endif if has_wayland subdir('wayland') diff --git a/src/platform/wayland/api.h b/src/platform/wayland/api.h index e5e933e..6c3154c 100644 --- a/src/platform/wayland/api.h +++ b/src/platform/wayland/api.h @@ -38,5 +38,6 @@ bool wayland_surface_start_interactive_resize(struct wayland_surface *s, enum ya bool wayland_surface_show_window_menu(struct wayland_surface *s, struct yasl_vector2i pos); bool wayland_surface_drag_carry(struct wayland_surface *s, struct yasl_vector2i offset); bool wayland_surface_place_under_pointer(struct wayland_surface *s, struct yasl_vector2i at); +bool wayland_surface_bell(struct wayland_surface *s); #endif diff --git a/src/platform/wayland/backend.c b/src/platform/wayland/backend.c index c3c7bed..c6272ce 100644 --- a/src/platform/wayland/backend.c +++ b/src/platform/wayland/backend.c @@ -177,6 +177,11 @@ static bool impl_surface_place_under_pointer(struct yasl_surface *s, struct yasl return wayland_surface_place_under_pointer((struct wayland_surface *)s, at); } +static bool impl_surface_bell(struct yasl_surface *s) +{ + return wayland_surface_bell((struct wayland_surface *)s); +} + static void *impl_display_get_native_handle(struct yasl_display *d) { struct wayland_display *w = (struct wayland_display *)d; @@ -225,6 +230,7 @@ const struct yasl_backend_ops yasl_wayland_backend = { .surface_show_window_menu = impl_surface_show_window_menu, .surface_drag_carry = impl_surface_drag_carry, .surface_place_under_pointer = impl_surface_place_under_pointer, + .surface_bell = impl_surface_bell, .display_get_native_handle = impl_display_get_native_handle, .surface_get_native_handle = impl_surface_get_native_handle, }; diff --git a/src/platform/wayland/internal.h b/src/platform/wayland/internal.h index c35db7f..84a603d 100644 --- a/src/platform/wayland/internal.h +++ b/src/platform/wayland/internal.h @@ -25,6 +25,7 @@ #include "protocols/primary-selection-unstable-v1-client-protocol.h" #include "protocols/relative-pointer-unstable-v1-client-protocol.h" #include "protocols/pointer-constraints-unstable-v1-client-protocol.h" +#include "protocols/xdg-system-bell-v1-client-protocol.h" #include <fcntl.h> #include <limits.h> @@ -217,6 +218,7 @@ struct wayland_display { struct wp_cursor_shape_manager_v1 *cursor_shape_mgr; struct zwp_relative_pointer_manager_v1 *relative_pointer_mgr; struct zwp_pointer_constraints_v1 *pointer_constraints; + struct xdg_system_bell_v1 *system_bell; struct wl_cursor_theme *cursor_theme; const char *cursor_theme_name; struct zwp_tablet_manager_v2 *tablet_mgr; diff --git a/src/platform/wayland/shell.c b/src/platform/wayland/shell.c index a12bd4d..8795989 100644 --- a/src/platform/wayland/shell.c +++ b/src/platform/wayland/shell.c @@ -214,6 +214,8 @@ static void registry_global(void *data, struct wl_registry *registry, } else if (strcmp(interface, zwp_pointer_constraints_v1_interface.name) == 0) { c->pointer_constraints = wl_registry_bind(registry, name, &zwp_pointer_constraints_v1_interface, 1); + } else if (strcmp(interface, xdg_system_bell_v1_interface.name) == 0) { + c->system_bell = wl_registry_bind(registry, name, &xdg_system_bell_v1_interface, 1); } else if (strcmp(interface, wp_fractional_scale_manager_v1_interface.name) == 0) { c->scale_mgr = wl_registry_bind(registry, name, &wp_fractional_scale_manager_v1_interface, 1); @@ -403,3 +405,10 @@ int surface_init_shell(struct wayland_surface *w, const char *title) } return 0; } + +bool wayland_surface_bell(struct wayland_surface *w) +{ + if (!w || w->closed || !w->conn || !w->conn->system_bell || !w->surface) return false; + xdg_system_bell_v1_ring(w->conn->system_bell, w->surface); + return true; +} diff --git a/src/platform/wayland/surface.c b/src/platform/wayland/surface.c index 0f6d7d7..cf1877d 100644 --- a/src/platform/wayland/surface.c +++ b/src/platform/wayland/surface.c @@ -202,6 +202,7 @@ static void display_free(struct wayland_display *c) if (c->cursor_shape_mgr) wp_cursor_shape_manager_v1_destroy(c->cursor_shape_mgr); if (c->relative_pointer_mgr) zwp_relative_pointer_manager_v1_destroy(c->relative_pointer_mgr); if (c->pointer_constraints) zwp_pointer_constraints_v1_destroy(c->pointer_constraints); + if (c->system_bell) xdg_system_bell_v1_destroy(c->system_bell); if (c->cursor_theme) wl_cursor_theme_destroy(c->cursor_theme); if (c->tablet_mgr) zwp_tablet_manager_v2_destroy(c->tablet_mgr); if (c->text_input_mgr) zwp_text_input_manager_v3_destroy(c->text_input_mgr); @@ -246,6 +247,7 @@ struct wayland_display *wayland_display_open(struct yasl_loop *loop, const struc c->cbs = &c->base.callbacks; c->ud = spec->ud; if (spec->app_id) c->app_id = strdup(spec->app_id); + c->base.app_id = c->app_id; } c->xkb_ctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS); c->wl = c->xkb_ctx ? wl_display_connect(NULL) : NULL; diff --git a/src/platform/x11/window.c b/src/platform/x11/window.c index f60a878..be1ff32 100644 --- a/src/platform/x11/window.c +++ b/src/platform/x11/window.c @@ -1601,6 +1601,7 @@ static struct yasl_display *display_open(struct yasl_loop *loop, } d->base.loop = d->loop; d->app_id = strdup(spec && spec->app_id ? spec->app_id : "YASL"); + d->base.app_id = d->app_id; if (!d->loop || !d->app_id) { if (d->owns_loop) yasl_loop_destroy(d->loop); free(d->app_id); @@ -1803,6 +1804,18 @@ static bool place(struct yasl_surface *base, struct yasl_vector2i at) return true; } +static bool bell(struct yasl_surface *base) +{ + struct x11_surface *s = (struct x11_surface *)base; + struct x11_display *d = (struct x11_display *)base->display; + if (s->destroyed || !d->native) return false; + /* The XKB bell names the window, so the window manager can flash it. */ + if (!XkbBell(d->native, s->window, 0, None)) + XBell(d->native, 0); + XFlush(d->native); + return true; +} + static void drag_end(struct x11_display *d, enum yasl_drag_end how) { if (!d || !d->drag.active) return; @@ -2045,6 +2058,7 @@ const struct yasl_backend_ops yasl_x11_backend = { .surface_set_cursor = cursor, .surface_drag_carry = drag_carry, .surface_place_under_pointer = place, + .surface_bell = bell, .surface_start_interactive_move = interactive_move, .surface_start_interactive_resize = interactive_resize, .surface_show_window_menu = show_window_menu, |
