diff options
-rw-r--r-- | .gitreview | 5 | ||||
-rw-r--r-- | COPYING | 24 | ||||
-rw-r--r-- | meson.build | 102 | ||||
-rw-r--r-- | shared/os-compatibility.c | 404 | ||||
-rw-r--r-- | shared/os-compatibility.h | 72 | ||||
-rw-r--r-- | src/main.c | 905 |
6 files changed, 1512 insertions, 0 deletions
diff --git a/.gitreview b/.gitreview new file mode 100644 index 0000000..c54f0de --- /dev/null +++ b/.gitreview @@ -0,0 +1,5 @@ +[gerrit] +host=gerrit.automotivelinux.org +port=29418 +project=src/native-shell-client +defaultbranch=master @@ -0,0 +1,24 @@ +SPDX-License-Identifier: MIT + +Copyright © 2022 Collabora, Ltd. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice (including the +next paragraph) shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..033bf95 --- /dev/null +++ b/meson.build @@ -0,0 +1,102 @@ +project('native-shell-client', + 'c', + version: '0.0.1', + default_options: [ + 'warning_level=3', + 'c_std=gnu99', + ], + meson_version: '>= 0.60', + license: 'MIT/Expat', +) + +cc = meson.get_compiler('c') +add_project_arguments( + cc.get_supported_arguments([ + '-Wno-unused-parameter', + '-Wno-pedantic', + ]), + language: 'c' +) + +add_project_arguments([ + '-DPACKAGE_STRING="native-app @0@"'.format(meson.project_version()), + '-D_GNU_SOURCE', + '-D_ALL_SOURCE', + ], + language: 'c' +) + +optional_libc_funcs = [ 'memfd_create', 'strchrnul' ] +foreach func: optional_libc_funcs + if cc.has_function(func) + add_project_arguments('-DHAVE_@0@=1'.format(func.to_upper()), language: 'c') + endif +endforeach + +dep_scanner = dependency('wayland-scanner', native: true) +prog_scanner = find_program(dep_scanner.get_variable('wayland_scanner')) +dep_wp = dependency('wayland-protocols', version: '>= 1.12') +dir_wp_base = dep_wp.get_variable('pkgdatadir') +agl_compositor_dep = dependency('agl-compositor-0.0.20-protocols') +dir_agl_compositor_base = agl_compositor_dep.get_variable('pkgdatadir') + +xdg_shell_xml = join_paths(dir_wp_base, 'stable', 'xdg-shell', 'xdg-shell.xml') + +protocols = [ + { 'name': 'agl-shell', 'source': 'agl-compositor' }, + { 'name': 'xdg-shell', 'source': 'wp-stable' }, +] + +foreach proto: protocols + proto_name = proto['name'] + if proto['source'] == 'agl-compositor' + base_file = proto_name + xml_path = join_paths(dir_agl_compositor_base, '@0@.xml'.format(base_file)) + elif proto['source'] == 'wp-stable' + base_file = proto_name + xml_path = join_paths(dir_wp_base, 'stable', proto_name, '@0@.xml'.format(base_file)) + endif + + foreach output_type: [ 'client-header', 'server-header', 'private-code' ] + if output_type == 'client-header' + output_file = '@0@-client-protocol.h'.format(base_file) + elif output_type == 'server-header' + output_file = '@0@-server-protocol.h'.format(base_file) + else + output_file = '@0@-protocol.c'.format(base_file) + if dep_scanner.version().version_compare('< 1.14.91') + output_type = 'code' + endif + endif + + var_name = output_file.underscorify() + target = custom_target( + '@0@ @1@'.format(base_file, output_type), + command: [ prog_scanner, output_type, '@INPUT@', '@OUTPUT@' ], + input: xml_path, + output: output_file, + ) + + set_variable(var_name, target) + endforeach +endforeach + +deps_wayland = [ + dependency('wayland-client'), +] + +srcs_native_app = [ + 'shared/os-compatibility.c', + 'src/main.c', + agl_shell_client_protocol_h, + xdg_shell_client_protocol_h, + xdg_shell_protocol_c, + agl_shell_protocol_c, +] + +exe_agl_compositor = executable( + 'native-shell-client', + srcs_native_app, + dependencies: deps_wayland, + install: true +) diff --git a/shared/os-compatibility.c b/shared/os-compatibility.c new file mode 100644 index 0000000..3638887 --- /dev/null +++ b/shared/os-compatibility.c @@ -0,0 +1,404 @@ +/* SPDX-License-Identifier: MIT + * Copyright © 2012 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include <sys/types.h> +#include <sys/socket.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <sys/epoll.h> +#include <string.h> +#include <stdlib.h> + +#ifdef HAVE_MEMFD_CREATE +#include <sys/mman.h> +#endif + +#include "os-compatibility.h" + +#define READONLY_SEALS (F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE) + +int +os_fd_set_cloexec(int fd) +{ + long flags; + + if (fd == -1) + return -1; + + flags = fcntl(fd, F_GETFD); + if (flags == -1) + return -1; + + if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1) + return -1; + + return 0; +} + +static int +set_cloexec_or_close(int fd) +{ + if (os_fd_set_cloexec(fd) != 0) { + close(fd); + return -1; + } + return fd; +} + +int +os_socketpair_cloexec(int domain, int type, int protocol, int *sv) +{ + int ret; + +#ifdef SOCK_CLOEXEC + ret = socketpair(domain, type | SOCK_CLOEXEC, protocol, sv); + if (ret == 0 || errno != EINVAL) + return ret; +#endif + + ret = socketpair(domain, type, protocol, sv); + if (ret < 0) + return ret; + + sv[0] = set_cloexec_or_close(sv[0]); + sv[1] = set_cloexec_or_close(sv[1]); + + if (sv[0] != -1 && sv[1] != -1) + return 0; + + close(sv[0]); + close(sv[1]); + return -1; +} + +int +os_epoll_create_cloexec(void) +{ + int fd; + +#ifdef EPOLL_CLOEXEC + fd = epoll_create1(EPOLL_CLOEXEC); + if (fd >= 0) + return fd; + if (errno != EINVAL) + return -1; +#endif + + fd = epoll_create(1); + return set_cloexec_or_close(fd); +} + +static int +create_tmpfile_cloexec(char *tmpname) +{ + int fd; + +#ifdef HAVE_MKOSTEMP + fd = mkostemp(tmpname, O_CLOEXEC); + if (fd >= 0) + unlink(tmpname); +#else + fd = mkstemp(tmpname); + if (fd >= 0) { + fd = set_cloexec_or_close(fd); + unlink(tmpname); + } +#endif + + return fd; +} + +/* + * Create a new, unique, anonymous file of the given size, and + * return the file descriptor for it. The file descriptor is set + * CLOEXEC. The file is immediately suitable for mmap()'ing + * the given size at offset zero. + * + * The file should not have a permanent backing store like a disk, + * but may have if XDG_RUNTIME_DIR is not properly implemented in OS. + * + * The file name is deleted from the file system. + * + * The file is suitable for buffer sharing between processes by + * transmitting the file descriptor over Unix sockets using the + * SCM_RIGHTS methods. + * + * If the C library implements posix_fallocate(), it is used to + * guarantee that disk space is available for the file at the + * given size. If disk space is insufficient, errno is set to ENOSPC. + * If posix_fallocate() is not supported, program may receive + * SIGBUS on accessing mmap()'ed file contents instead. + * + * If the C library implements memfd_create(), it is used to create the + * file purely in memory, without any backing file name on the file + * system, and then sealing off the possibility of shrinking it. This + * can then be checked before accessing mmap()'ed file contents, to + * make sure SIGBUS can't happen. It also avoids requiring + * XDG_RUNTIME_DIR. + */ +int +os_create_anonymous_file(off_t size) +{ + static const char template[] = "/weston-shared-XXXXXX"; + const char *path; + char *name; + int fd; + int ret; + +#ifdef HAVE_MEMFD_CREATE + fd = memfd_create("weston-shared", MFD_CLOEXEC | MFD_ALLOW_SEALING); + if (fd >= 0) { + /* We can add this seal before calling posix_fallocate(), as + * the file is currently zero-sized anyway. + * + * There is also no need to check for the return value, we + * couldn't do anything with it anyway. + */ + fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK); + } else +#endif + { + path = getenv("XDG_RUNTIME_DIR"); + if (!path) { + errno = ENOENT; + return -1; + } + + name = malloc(strlen(path) + sizeof(template)); + if (!name) + return -1; + + strcpy(name, path); + strcat(name, template); + + fd = create_tmpfile_cloexec(name); + + free(name); + + if (fd < 0) + return -1; + } + +#ifdef HAVE_POSIX_FALLOCATE + do { + ret = posix_fallocate(fd, 0, size); + } while (ret == EINTR); + if (ret != 0) { + close(fd); + errno = ret; + return -1; + } +#else + do { + ret = ftruncate(fd, size); + } while (ret < 0 && errno == EINTR); + if (ret < 0) { + close(fd); + return -1; + } +#endif + + return fd; +} + +#ifndef HAVE_STRCHRNUL +char * +strchrnul(const char *s, int c) +{ + while (*s && *s != c) + s++; + return (char *)s; +} +#endif + +struct ro_anonymous_file { + int fd; + size_t size; +}; + +/** Create a new anonymous read-only file of the given size and the given data + * + * \param size The size of \p data. + * \param data The data of the file with the size \p size. + * \return A new \c ro_anonymous_file, or NULL on failure. + * + * The intended use-case is for sending mid-sized data from the compositor + * to clients. + * If the function fails errno is set. + */ +struct ro_anonymous_file * +os_ro_anonymous_file_create(size_t size, + const char *data) +{ + struct ro_anonymous_file *file; + void *map; + + file = calloc(1, sizeof *file); + if (!file) { + errno = ENOMEM; + return NULL; + } + + file->size = size; + file->fd = os_create_anonymous_file(size); + if (file->fd == -1) + goto err_free; + + map = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, file->fd, 0); + if (map == MAP_FAILED) + goto err_close; + + memcpy(map, data, size); + + munmap(map, size); + +#ifdef HAVE_MEMFD_CREATE + /* try to put seals on the file to make it read-only so that we can + * return the fd later directly when support_shared is not set. + * os_ro_anonymous_file_get_fd can handle the fd even if it is not + * sealed read-only and will instead create a new anonymous file on + * each invocation. + */ + fcntl(file->fd, F_ADD_SEALS, READONLY_SEALS); +#endif + + return file; + +err_close: + close(file->fd); +err_free: + free(file); + return NULL; +} + +/** Destroy an anonymous read-only file + * + * \param file The file to destroy. + */ +void +os_ro_anonymous_file_destroy(struct ro_anonymous_file *file) +{ + close(file->fd); + free(file); +} + +/** Get the size of an anonymous read-only file + * + * \param file The file to get the size of. + * \return The size of the file. + */ +size_t +os_ro_anonymous_file_size(struct ro_anonymous_file *file) +{ + return file->size; +} + +/** Returns a file descriptor for the given file, ready to be send to a client. + * + * \param file The file for which to get a file descriptor. + * \param mapmode Describes the ways in which the returned file descriptor can + * be used with mmap. + * \return A file descriptor for the given file that can be send to a client + * or -1 on failure. + * + * The returned file descriptor must not be shared between multiple clients. + * When \p mapmode is RO_ANONYMOUS_FILE_MAPMODE_PRIVATE the file descriptor is + * only guaranteed to be mmapable with \c MAP_PRIVATE, when \p mapmode is + * RO_ANONYMOUS_FILE_MAPMODE_SHARED the file descriptor can be mmaped with + * either MAP_PRIVATE or MAP_SHARED. + * When you're done with the fd you must call \c os_ro_anonymous_file_put_fd + * instead of calling \c close. + * If the function fails errno is set. + */ +int +os_ro_anonymous_file_get_fd(struct ro_anonymous_file *file, + enum ro_anonymous_file_mapmode mapmode) +{ + void *src, *dst; + int seals, fd; + + seals = fcntl(file->fd, F_GET_SEALS); + + /* file was sealed for read-only and we don't have to support MAP_SHARED + * so we can simply pass the memfd fd + */ + if (seals != -1 && mapmode == RO_ANONYMOUS_FILE_MAPMODE_PRIVATE && + (seals & READONLY_SEALS) == READONLY_SEALS) + return file->fd; + + /* for all other cases we create a new anonymous file that can be mapped + * with MAP_SHARED and copy the contents to it and return that instead + */ + fd = os_create_anonymous_file(file->size); + if (fd == -1) + return fd; + + src = mmap(NULL, file->size, PROT_READ, MAP_PRIVATE, file->fd, 0); + if (src == MAP_FAILED) { + close(fd); + return -1; + } + + dst = mmap(NULL, file->size, PROT_WRITE, MAP_SHARED, fd, 0); + if (dst == MAP_FAILED) { + close(fd); + munmap(src, file->size); + return -1; + } + + memcpy(dst, src, file->size); + munmap(src, file->size); + munmap(dst, file->size); + + return fd; +} + +/** Release a file descriptor returned by \c os_ro_anonymous_file_get_fd + * + * \param fd A file descriptor returned by \c os_ro_anonymous_file_get_fd. + * \return 0 on success, or -1 on failure. + * + * This function must be called for every file descriptor created with + * \c os_ro_anonymous_file_get_fd to not leake any resources. + * If the function fails errno is set. + */ +int +os_ro_anonymous_file_put_fd(int fd) +{ + int seals = fcntl(fd, F_GET_SEALS); + if (seals == -1 && errno != EINVAL) + return -1; + + /* If the fd cannot be sealed seals is -1 at this point + * or the file can be sealed but has not been sealed for writing. + * In both cases we created a new anonymous file that we have to + * close. + */ + if (seals == -1 || !(seals & F_SEAL_WRITE)) + close(fd); + + return 0; +} diff --git a/shared/os-compatibility.h b/shared/os-compatibility.h new file mode 100644 index 0000000..1eaf5e8 --- /dev/null +++ b/shared/os-compatibility.h @@ -0,0 +1,72 @@ +/* SPDX-License-Identifier: MIT + * Copyright © 2012 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef OS_COMPATIBILITY_H +#define OS_COMPATIBILITY_H + +#include <sys/types.h> + +int +os_fd_set_cloexec(int fd); + +int +os_socketpair_cloexec(int domain, int type, int protocol, int *sv); + +int +os_epoll_create_cloexec(void); + +int +os_create_anonymous_file(off_t size); + +#ifndef HAVE_STRCHRNUL +char * +strchrnul(const char *s, int c); +#endif + +struct ro_anonymous_file; + +enum ro_anonymous_file_mapmode { + RO_ANONYMOUS_FILE_MAPMODE_PRIVATE, + RO_ANONYMOUS_FILE_MAPMODE_SHARED, +}; + +struct ro_anonymous_file * +os_ro_anonymous_file_create(size_t size, + const char *data); + +void +os_ro_anonymous_file_destroy(struct ro_anonymous_file *file); + +size_t +os_ro_anonymous_file_size(struct ro_anonymous_file *file); + +int +os_ro_anonymous_file_get_fd(struct ro_anonymous_file *file, + enum ro_anonymous_file_mapmode mapmode); + +int +os_ro_anonymous_file_put_fd(int fd); + +#endif /* OS_COMPATIBILITY_H */ diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..30a241e --- /dev/null +++ b/src/main.c @@ -0,0 +1,905 @@ +/* SPDX-License-Identifier: MIT + * Copyright © 2022 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include <assert.h> +#include <errno.h> +#include <signal.h> +#include <stdarg.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/mman.h> +#include <linux/input.h> + +#include <wayland-client.h> +#include "shared/os-compatibility.h" + +#include "agl-shell-client-protocol.h" +#include "xdg-shell-client-protocol.h" + +#define DEFAULT_WIDTH_SIZE 150 +#define DEFAULT_HEIGHT_SIZE 150 + +#ifndef MAX +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#endif + +struct data_output { + int width, height; + int offset_x, offset_y; +}; + +enum window_type { + WINDOW_NORMAL, + WINDOW_BG, + WINDOW_TOP, + WINDOW_BOTTOM +}; + +const char *names[] = { "normal", "bg", "top", "bottom" }; + +struct display { + struct wl_display *display; + struct wl_registry *registry; + + struct wl_compositor *compositor; + struct xdg_wm_base *wm_base; + + struct agl_shell *agl_shell; + struct wl_output *output; + + struct data_output doutput; + + struct wl_shm *shm; + bool has_xrgb; + + struct wl_seat *seat; + struct wl_pointer *pointer; + struct wl_touch *touch; + struct wl_keyboard *keyboard; +}; + +struct buffer { + struct wl_buffer *buffer; + void *shm_data; + int busy; +}; + +struct window { + struct display *display; + int width, height; + + struct wl_surface *surface; + struct xdg_surface *xdg_surface; + struct xdg_toplevel *xdg_toplevel; + + struct buffer buffers[2]; + struct buffer *prev_buffer; + struct wl_callback *callback; + bool wait_for_configure; + + int fullscreen, maximized; + enum window_type type; +}; + +static int running = 1; + +static void +redraw(void *data, struct wl_callback *callback, uint32_t _data); + +static void +buffer_release(void *data, struct wl_buffer *buffer) +{ + struct buffer *mybuf = data; + mybuf->busy = 0; +} + +static const struct wl_buffer_listener buffer_listener = { + buffer_release +}; + +static int +create_shm_buffer(struct display *display, struct buffer *buffer, + int width, int height, uint32_t format) +{ + struct wl_shm_pool *pool; + int fd, size, stride; + void *data; + + stride = width * 4; + size = stride * height; + + fd = os_create_anonymous_file(size); + if (fd < 0) { + fprintf(stderr, "creating a buffer file for %d B failed: %s\n", + size, strerror(errno)); + return -1; + } + + data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (data == MAP_FAILED) { + fprintf(stderr, "mmap failed: %s\n", strerror(errno)); + close(fd); + return -1; + } + + pool = wl_shm_create_pool(display->shm, fd, size); + buffer->buffer = wl_shm_pool_create_buffer(pool, 0, + width, height, + stride, format); + wl_buffer_add_listener(buffer->buffer, &buffer_listener, buffer); + wl_shm_pool_destroy(pool); + close(fd); + + buffer->shm_data = data; + return 0; +} + +static void +handle_xdg_surface_configure(void *data, struct xdg_surface *surface, + uint32_t serial) +{ + struct window *window = data; + + xdg_surface_ack_configure(surface, serial); + + if (window->wait_for_configure) { + fprintf(stderr, "got a configure event, doing a redraw\n"); + redraw(window, NULL, 0); + window->wait_for_configure = false; + } +} + +static const struct xdg_surface_listener xdg_surface_listener = { + handle_xdg_surface_configure, +}; + +static void +handle_xdg_toplevel_configure(void *data, struct xdg_toplevel *xdg_toplevel, + int32_t width, int32_t height, struct wl_array *states) +{ + struct window *window = data; + uint32_t *p; + + window->fullscreen = 0; + window->maximized = 0; + + wl_array_for_each(p, states) { + uint32_t state = *p; + switch (state) { + case XDG_TOPLEVEL_STATE_FULLSCREEN: + window->fullscreen = 1; + break; + case XDG_TOPLEVEL_STATE_MAXIMIZED: + window->maximized = 1; + break; + } + } + + fprintf(stdout, "Got handle_xdg_toplevel_configure() " + "width %d, height %d, full %d, max %d, type %d\n", width, height, + window->fullscreen, window->maximized, window->type); + + if (width > 0 && height > 0) { + if (!window->fullscreen && !window->maximized) { + window->width = width; + window->height = height; + } + window->width = width; + window->height = height; + } else if (!window->fullscreen && !window->maximized) { + /* FIXME: should actually use the window geometry here, first + * assigned to derive the the width/height in case we can a + * configure event for panels */ + if (width == 0) + window->width = DEFAULT_WIDTH_SIZE; + else + window->width = width; + + if (height == 0) + window->height = DEFAULT_HEIGHT_SIZE; + else + window->height = height; + } + + fprintf(stdout, "settting width %d, height %d\n", + window->width, window->height); +} + +static void +handle_xdg_toplevel_close(void *data, struct xdg_toplevel *xdg_toplevel) +{ + running = 0; +} + + +static void handle_xdg_toplevel_configure_caps(void *data, struct + xdg_toplevel *xdg_toplevel, struct wl_array *caps) +{ + +} + +static void handle_xdg_toplevel_configure_bounds(void *data, struct + xdg_toplevel *xdg_toplevel, int32_t width, int32_t height) +{ +} + +static const struct xdg_toplevel_listener xdg_toplevel_listener = { + handle_xdg_toplevel_configure, + handle_xdg_toplevel_close, + handle_xdg_toplevel_configure_bounds, + handle_xdg_toplevel_configure_caps, +}; + +static struct window * +create_window(struct display *display, int width, int height, enum window_type type) +{ + struct window *window; + const char *name = NULL; + + window = calloc(1, sizeof *window); + if (!window) + return NULL; + + window->callback = NULL; + window->display = display; + window->width = width; + window->height = height; + window->type = type; + window->surface = wl_compositor_create_surface(display->compositor); + + if (display->wm_base) { + window->xdg_surface = + xdg_wm_base_get_xdg_surface(display->wm_base, window->surface); + assert(window->xdg_surface); + + xdg_surface_add_listener(window->xdg_surface, + &xdg_surface_listener, window); + window->xdg_toplevel = xdg_surface_get_toplevel(window->xdg_surface); + assert(window->xdg_toplevel); + + xdg_toplevel_add_listener(window->xdg_toplevel, + &xdg_toplevel_listener, window); + + name = names[window->type]; + xdg_toplevel_set_title(window->xdg_toplevel, name); + xdg_toplevel_set_app_id(window->xdg_toplevel, name); + wl_surface_commit(window->surface); + + window->wait_for_configure = true; + } else { + assert(0); + } + + return window; +} + +static void +destroy_window(struct window *window) +{ + if (window->callback) + wl_callback_destroy(window->callback); + + if (window->buffers[0].buffer) + wl_buffer_destroy(window->buffers[0].buffer); + if (window->buffers[1].buffer) + wl_buffer_destroy(window->buffers[1].buffer); + + if (window->xdg_toplevel) + xdg_toplevel_destroy(window->xdg_toplevel); + + if (window->xdg_surface) + xdg_surface_destroy(window->xdg_surface); + + wl_surface_destroy(window->surface); + free(window); +} + +static struct buffer * +window_next_buffer(struct window *window) +{ + struct buffer *buffer; + int ret = 0; + + if (!window->buffers[0].busy) + buffer = &window->buffers[0]; + else if (!window->buffers[1].busy) + buffer = &window->buffers[1]; + else + return NULL; + + if (!buffer->buffer) { + ret = create_shm_buffer(window->display, buffer, + window->width, window->height, + WL_SHM_FORMAT_XRGB8888); + + if (ret < 0) + return NULL; + + /* paint the padding */ + memset(buffer->shm_data, 0xff, window->width * window->height * 4); + } + + return buffer; +} + +static void +paint_pixels_top(void *image, int padding, int width, int height) +{ + memset(image, 0xa0, width * height * 4); +} + +static void +paint_pixels_bottom(void *image, int padding, int width, int height) +{ + memset(image, 0x00, width * height * 4); +} + +static void +paint_pixels_bg(void *image, int padding, int width, int height) +{ + memset(image, 0x00, width * height * 4); +} + +static const struct wl_callback_listener frame_listener; + +static void +redraw(void *data, struct wl_callback *callback, uint32_t _data) +{ + struct window *window = data; + struct buffer *buffer; + (void) _data; + static int repaints = 2; + + buffer = window_next_buffer(window); + if (!buffer) { + fprintf(stderr, + !callback ? "Failed to create the first buffer.\n" : + "Both buffers busy at redraw(). Server bug?\n"); + abort(); + } + + switch (window->type) { + case WINDOW_NORMAL: + default: + assert(!"Ooops\n"); + case WINDOW_BOTTOM: + paint_pixels_bottom(buffer->shm_data, 0, window->width, window->height); + break; + case WINDOW_TOP: + paint_pixels_top(buffer->shm_data, 0, window->width, window->height); + break; + case WINDOW_BG: + paint_pixels_bg(buffer->shm_data, 0, window->width, window->height); + break; + } + + wl_surface_attach(window->surface, buffer->buffer, 0, 0); + wl_surface_damage(window->surface, 0, 0, window->width, window->height); + + if (callback) { + wl_callback_destroy(callback); + repaints--; + } + + if (repaints == 0) + return; + + window->callback = wl_surface_frame(window->surface); + wl_callback_add_listener(window->callback, &frame_listener, window); + wl_surface_commit(window->surface); + + buffer->busy = 1; +} + +static const struct wl_callback_listener frame_listener = { + redraw +}; + +static void +shm_format(void *data, struct wl_shm *wl_shm, uint32_t format) +{ + struct display *d = data; + + if (format == WL_SHM_FORMAT_XRGB8888) + d->has_xrgb = true; +} + +struct wl_shm_listener shm_listener = { + shm_format +}; + +static void +xdg_wm_base_ping(void *data, struct xdg_wm_base *shell, uint32_t serial) +{ + xdg_wm_base_pong(shell, serial); +} + +static const struct xdg_wm_base_listener xdg_wm_base_listener = { + xdg_wm_base_ping, +}; + +static void +display_handle_geometry(void *data, struct wl_output *wl_output, + int x, int y, int physical_width, int physical_height, + int subpixel, const char *make, const char *model, int transform) +{ + +} + +static void +display_handle_mode(void *data, struct wl_output *wl_output, uint32_t flags, + int width, int height, int refresh) +{ + struct display *d = data; + + if (wl_output == d->output && (flags & WL_OUTPUT_MODE_CURRENT)) { + d->doutput.width = width; + d->doutput.height = height; + } + +} + +static void +display_handle_done(void *data, struct wl_output *output) +{ + +} + +static void +display_handle_scale(void *data, struct wl_output *output, int32_t factor) +{ +} + +static void +display_handle_name(void *data, struct wl_output *wl_output, const char *name) +{ +} + +static void +display_handle_desc(void *data, struct wl_output *wl_output, const char *desc) +{ +} + +static const struct wl_output_listener output_listener = { + display_handle_geometry, + display_handle_mode, + display_handle_done, + display_handle_scale, + display_handle_name, + display_handle_desc, +}; + + +static void +pointer_handle_enter(void *data, struct wl_pointer *pointer, + uint32_t serial, struct wl_surface *surface, + wl_fixed_t sx, wl_fixed_t sy) +{ +} + + +static void +pointer_handle_leave(void *data, struct wl_pointer *pointer, + uint32_t serial, struct wl_surface *surface) +{ +} + +static void +pointer_handle_motion(void *data, struct wl_pointer *pointer, + uint32_t time, wl_fixed_t sx, wl_fixed_t sy) +{ +} + +static void +pointer_handle_button(void *data, struct wl_pointer *wl_pointer, + uint32_t serial, uint32_t time, uint32_t button, + uint32_t state) +{ +} + +static void +pointer_handle_axis(void *data, struct wl_pointer *wl_pointer, + uint32_t time, uint32_t axis, wl_fixed_t value) +{ +} + +static void +pointer_handle_frame(void *data, struct wl_pointer *pointer) +{ +} + +static void +pointer_handle_axis_source(void *data, struct wl_pointer *pointer, uint32_t axis_source) +{ +} + +static void +pointer_handle_axis_stop(void *data, struct wl_pointer *wl_pointer, + uint32_t time, uint32_t axis) +{ +} + +static void +pointer_handle_axis_discrete(void *data, struct wl_pointer *pointer, + uint32_t axis, int32_t discrete) +{ +} + +static void +pointer_handle_axis_value120(void *data, struct wl_pointer *wl_pointer, uint32_t axis, int32_t value120) +{ +} + +static const struct wl_pointer_listener pointer_listener = { + pointer_handle_enter, + pointer_handle_leave, + pointer_handle_motion, + pointer_handle_button, + pointer_handle_axis, + pointer_handle_frame, + pointer_handle_axis_source, + pointer_handle_axis_stop, + pointer_handle_axis_discrete, + pointer_handle_axis_value120, + +}; + + +static void +touch_handle_down(void *data, struct wl_touch *wl_touch, + uint32_t serial, uint32_t time, struct wl_surface *surface, + int32_t id, wl_fixed_t x_w, wl_fixed_t y_w) +{ +} + +static void +touch_handle_up(void *data, struct wl_touch *wl_touch, + uint32_t serial, uint32_t time, int32_t id) +{ +} + +static void +touch_handle_motion(void *data, struct wl_touch *wl_touch, + uint32_t time, int32_t id, wl_fixed_t x_w, wl_fixed_t y_w) +{ +} + +static void +touch_handle_frame(void *data, struct wl_touch *wl_touch) +{ +} + +static void +touch_handle_cancel(void *data, struct wl_touch *wl_touch) +{ +} + +static void +touch_handle_shape(void *data, struct wl_touch *touch, int32_t id, wl_fixed_t major, wl_fixed_t minor) +{ + +} + +static void +touch_handle_orientation(void *data, struct wl_touch *wl_touch, int32_t id, wl_fixed_t orientation) +{ +} + +static const struct wl_touch_listener touch_listener = { + touch_handle_down, + touch_handle_up, + touch_handle_motion, + touch_handle_frame, + touch_handle_cancel, + touch_handle_shape, + touch_handle_orientation, +}; + + +static void +keyboard_handle_keymap(void *data, struct wl_keyboard *keyboard, + uint32_t format, int fd, uint32_t size) +{ + /* Just so we don’t leak the keymap fd */ + close(fd); +} + +static void +keyboard_handle_enter(void *data, struct wl_keyboard *keyboard, + uint32_t serial, struct wl_surface *surface, + struct wl_array *keys) +{ +} + +static void +keyboard_handle_leave(void *data, struct wl_keyboard *keyboard, + uint32_t serial, struct wl_surface *surface) +{ +} + +static void +keyboard_handle_key(void *data, struct wl_keyboard *keyboard, + uint32_t serial, uint32_t time, uint32_t key, + uint32_t state) +{ + struct display *d = data; + + if (!d->wm_base) + return; + +} + +static void +keyboard_handle_modifiers(void *data, struct wl_keyboard *keyboard, + uint32_t serial, uint32_t mods_depressed, + uint32_t mods_latched, uint32_t mods_locked, + uint32_t group) +{ +} + + +static void +keyboard_handle_repeat_info(void *data, struct wl_keyboard *wl_keyboard, int32_t rate, int32_t delay) +{ +} + +static const struct wl_keyboard_listener keyboard_listener = { + keyboard_handle_keymap, + keyboard_handle_enter, + keyboard_handle_leave, + keyboard_handle_key, + keyboard_handle_modifiers, + keyboard_handle_repeat_info, +}; + +static void +seat_handle_capabilities(void *data, struct wl_seat *seat, enum wl_seat_capability caps) +{ + struct display *d = data; + + if ((caps & WL_SEAT_CAPABILITY_POINTER) && !d->pointer) { + d->pointer = wl_seat_get_pointer(seat); + wl_pointer_add_listener(d->pointer, &pointer_listener, d); + } else if (!(caps & WL_SEAT_CAPABILITY_POINTER) && d->pointer) { + wl_pointer_destroy(d->pointer); + d->pointer = NULL; + } + + if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !d->keyboard) { + d->keyboard = wl_seat_get_keyboard(seat); + wl_keyboard_add_listener(d->keyboard, &keyboard_listener, d); + } else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD) && d->keyboard) { + wl_keyboard_destroy(d->keyboard); + d->keyboard = NULL; + } + + if ((caps & WL_SEAT_CAPABILITY_TOUCH) && !d->touch) { + d->touch = wl_seat_get_touch(seat); + wl_touch_set_user_data(d->touch, d); + wl_touch_add_listener(d->touch, &touch_listener, d); + } else if (!(caps & WL_SEAT_CAPABILITY_TOUCH) && d->touch) { + wl_touch_destroy(d->touch); + d->touch = NULL; + } +} + +static void +seat_handle_name(void *data, struct wl_seat *seat, const char *name) +{ + +} + +static const struct wl_seat_listener seat_listener = { + seat_handle_capabilities, + seat_handle_name, +}; + + +static void +registry_handle_global(void *data, struct wl_registry *registry, + uint32_t id, const char *interface, uint32_t version) +{ + struct display *d = data; + + if (strcmp(interface, "wl_compositor") == 0) { + d->compositor = wl_registry_bind(registry, id, + &wl_compositor_interface, 1); + } else if (strcmp(interface, "xdg_wm_base") == 0) { + d->wm_base = wl_registry_bind(registry, + id, &xdg_wm_base_interface, MAX(version, 3)); + xdg_wm_base_add_listener(d->wm_base, &xdg_wm_base_listener, d); + } else if (strcmp(interface, "wl_shm") == 0) { + d->shm = wl_registry_bind(registry, + id, &wl_shm_interface, 1); + wl_shm_add_listener(d->shm, &shm_listener, d); + } else if (strcmp(interface, "wl_seat") == 0) { + d->seat = wl_registry_bind(registry, id, + &wl_seat_interface, 1); + wl_seat_add_listener(d->seat, &seat_listener, d); + } else if (strcmp(interface, "agl_shell") == 0) { + d->agl_shell = wl_registry_bind(registry, id, + &agl_shell_interface, MAX(version, 1)); + } else if (strcmp(interface, "wl_output") == 0) { + d->output = wl_registry_bind(registry, id, + &wl_output_interface, + MAX(version, 3)); + wl_output_add_listener(d->output, &output_listener, d); + } +} + +static void +registry_handle_global_remove(void *data, struct wl_registry *registry, + uint32_t name) +{ + +} + +static const struct wl_registry_listener registry_listener = { + registry_handle_global, + registry_handle_global_remove +}; + +static struct display * +create_display(void) +{ + struct display *display; + + display = calloc(1, sizeof *display); + if (display == NULL) { + fprintf(stderr, "out of memory\n"); + exit(1); + } + display->display = wl_display_connect(NULL); + assert(display->display); + + display->has_xrgb = false; + display->registry = wl_display_get_registry(display->display); + + wl_registry_add_listener(display->registry, ®istry_listener, display); + wl_display_roundtrip(display->display); + + if (display->shm == NULL) { + fprintf(stderr, "No wl_shm global\n"); + exit(1); + } + + if (display->agl_shell == NULL) { + fprintf(stderr, "No agl_shell extension present\n"); + } + + wl_display_roundtrip(display->display); + + if (!display->has_xrgb) { + fprintf(stderr, "WL_SHM_FORMAT_XRGB32 not available\n"); + exit(1); + } + + return display; +} + +static void +destroy_display(struct display *display) +{ + if (display->shm) + wl_shm_destroy(display->shm); + + if (display->wm_base) + xdg_wm_base_destroy(display->wm_base); + + if (display->compositor) + wl_compositor_destroy(display->compositor); + + wl_registry_destroy(display->registry); + wl_display_flush(display->display); + wl_display_disconnect(display->display); + free(display); +} + +static void +signal_int(int signum) +{ + running = 0; +} + +static void +agl_shell_do_background(struct display *d, struct wl_surface *sufrace) +{ + if (d->agl_shell) { + agl_shell_set_background(d->agl_shell, sufrace, d->output); + } +} + +void +agl_shell_do_panel(struct display *d, struct wl_surface *sufrace, + enum agl_shell_edge mode) +{ + if (d->agl_shell) { + agl_shell_set_panel(d->agl_shell, sufrace, d->output, mode); + } +} + +static void +agl_shell_do_ready(struct display *d) +{ + if (d->agl_shell) { + agl_shell_ready(d->agl_shell); + } +} + +int +main(int argc, char *argv[]) +{ + struct sigaction sigint; + struct display *display; + + struct window *window; + + int ret = 0; + int set_ready = 0; + + sigint.sa_handler = signal_int; + sigemptyset(&sigint.sa_mask); + sigint.sa_flags = SA_RESETHAND | SA_SIGINFO; + sigaction(SIGINT, &sigint, NULL); + + display = create_display(); + if (!display) { + fprintf(stderr, "failed to create display!\n"); + exit(EXIT_FAILURE); + } + + window = create_window(display, 250, 250, WINDOW_BG); + if (!window) { + fprintf(stderr, "failed to create window!\n"); + exit(EXIT_FAILURE); + } + + /* Initialise damage to full surface, so the padding gets painted */ + wl_surface_damage(window->surface, 0, 0, + window->width, window->height); + + if (!window->wait_for_configure) { + fprintf(stderr, "wait_for_configure not set, doing a redraw\n"); + redraw(window, NULL, 0); + } + + agl_shell_do_background(display, window->surface); + + while (running && ret != -1) { + ret = wl_display_dispatch(display->display); + + if (set_ready == 0) { + agl_shell_do_ready(display); + set_ready = 1; + } + } + + destroy_window(window); + destroy_display(display); + + return 0; +} |