diff --git a/sommelier/.gitignore b/sommelier/.gitignore deleted file mode 100644 index dcbdc5f..0000000 --- a/sommelier/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -*-protocol.h -*.o -sommelier diff --git a/sommelier/BUILD.gn b/sommelier/BUILD.gn index 871c142..552b290 100644 --- a/sommelier/BUILD.gn +++ b/sommelier/BUILD.gn @@ -1,23 +1,18 @@ -# Copyright 2019 The Chromium OS Authors. All rights reserved. +# Copyright 2019 The ChromiumOS Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. +import("//common-mk/pkg_config.gni") import("wayland_protocol.gni") group("all") { - deps = [ - ":sommelier", - ":wayland_demo", - ":x11_demo", - ] -} - -if (!defined(peer_cmd_prefix)) { - if (use.amd64) { - peer_cmd_prefix = "\"/opt/google/cros-containers/lib/ld-linux-x86-64.so.2 --library-path /opt/google/cros-containers/lib --inhibit-rpath \\\"\\\"\"" + deps = [ ":sommelier" ] + if (use.fuzzer) { + deps += [ ":sommelier_wayland_fuzzer" ] } - if (use.arm) { - peer_cmd_prefix = "\"/opt/google/cros-containers/lib/ld-linux-armhf.so.3 --library-path /opt/google/cros-containers/lib --inhibit-rpath \\\"\\\"\"" + + if (use.test) { + deps += [ ":sommelier_test" ] } } @@ -31,21 +26,6 @@ if (!defined(xwayland_gl_driver_path)) { xwayland_gl_driver_path = "\"/opt/google/cros-containers/lib\"" } -# Set this to the shm driver to use for Xwayland. -if (!defined(xwayland_shm_driver)) { - xwayland_shm_driver = "\"virtwl\"" -} - -# Set this to the shm driver to use for wayland clients. -if (!defined(shm_driver)) { - shm_driver = "\"virtwl-dmabuf\"" -} - -# Set this to the virtwl device. -if (!defined(virtwl_device)) { - virtwl_device = "\"/dev/wl0\"" -} - # Set this to the frame color to use for Xwayland clients. if (!defined(frame_color)) { frame_color = "\"#f2f2f2\"" @@ -61,87 +41,131 @@ wayland_protocol_library("sommelier-protocol") { sources = [ "protocol/aura-shell.xml", "protocol/drm.xml", + "protocol/gaming-input-unstable-v2.xml", "protocol/gtk-shell.xml", "protocol/keyboard-extension-unstable-v1.xml", "protocol/linux-dmabuf-unstable-v1.xml", + "protocol/linux-explicit-synchronization-unstable-v1.xml", "protocol/pointer-constraints-unstable-v1.xml", "protocol/relative-pointer-unstable-v1.xml", + "protocol/text-input-extension-unstable-v1.xml", "protocol/text-input-unstable-v1.xml", + "protocol/text-input-x11-unstable-v1.xml", "protocol/viewporter.xml", - "protocol/xdg-shell-unstable-v6.xml", + "protocol/xdg-output-unstable-v1.xml", + "protocol/xdg-shell.xml", ] } +gaming_defines = [ "GAMEPAD_SUPPORT" ] +gaming_deps = [ ":libgaming" ] + +tracing_defines = [ "PERFETTO_TRACING" ] +tracing_pkg_deps = [ "perfetto" ] +tracing_libs = [ "pthread" ] + +sommelier_defines = [ + "_GNU_SOURCE", + "WL_HIDE_DEPRECATED", + "XWAYLAND_PATH=${xwayland_path}", + "XWAYLAND_GL_DRIVER_PATH=${xwayland_gl_driver_path}", + "FRAME_COLOR=${frame_color}", + "DARK_FRAME_COLOR=${dark_frame_color}", + ] + gaming_defines + tracing_defines + +static_library("libgaming") { + sources = [ "sommelier-gaming.cc" ] + defines = gaming_defines + pkg_deps = [ + "libevdev", + "pixman-1", + ] + deps = [ ":sommelier-protocol" ] +} + +static_library("libsommelier") { + sources = [ + "compositor/sommelier-compositor.cc", + "compositor/sommelier-drm.cc", + "compositor/sommelier-mmap.cc", + "compositor/sommelier-shm.cc", + "sommelier-ctx.cc", + "sommelier-data-device-manager.cc", + "sommelier-display.cc", + "sommelier-global.cc", + "sommelier-gtk-shell.cc", + "sommelier-output.cc", + "sommelier-pointer-constraints.cc", + "sommelier-relative-pointer-manager.cc", + "sommelier-seat.cc", + "sommelier-shell.cc", + "sommelier-subcompositor.cc", + "sommelier-text-input.cc", + "sommelier-timing.cc", + "sommelier-tracing.cc", + "sommelier-transform.cc", + "sommelier-util.cc", + "sommelier-viewporter.cc", + "sommelier-window.cc", + "sommelier-xdg-shell.cc", + "sommelier-xshape.cc", + "sommelier.cc", + "virtualization/virtgpu_channel.cc", + "virtualization/virtwl_channel.cc", + ] + include_dirs = [] + defines = sommelier_defines + pkg_deps = [ + "gbm", + "libdrm", + "pixman-1", + "wayland-client", + "wayland-server", + "xcb", + "xcb-composite", + "xcb-shape", + "xcb-xfixes", + "xkbcommon", + ] + tracing_pkg_deps + libs = [ "m" ] + tracing_libs + deps = [ ":sommelier-protocol" ] + gaming_deps +} + executable("sommelier") { - pkg_deps = [ - "gbm", - "grpc++", - "libdrm", - "pixman-1", - "protobuf", - "vm_protos", - "wayland-client", - "wayland-server", - "xcb", - "xcb-composite", - "xcb-xfixes", - "xkbcommon", - ] - libs = [ "m" ] - deps = [ - ":sommelier-protocol", - ] - sources = [ - "sommelier-compositor.c", - "sommelier-data-device-manager.c", - "sommelier-display.c", - "sommelier-drm.c", - "sommelier-gtk-shell.c", - "sommelier-output.c", - "sommelier-pointer-constraints.c", - "sommelier-relative-pointer-manager.c", - "sommelier-seat.c", - "sommelier-shell.c", - "sommelier-shm.c", - "sommelier-subcompositor.c", - "sommelier-text-input.c", - "sommelier-viewporter.c", - "sommelier-xdg-shell.c", - "sommelier.c", - ] - defines = [ - "_GNU_SOURCE", - "WL_HIDE_DEPRECATED", - "XWAYLAND_PATH=${xwayland_path}", - "XWAYLAND_GL_DRIVER_PATH=${xwayland_gl_driver_path}", - "XWAYLAND_SHM_DRIVER=${xwayland_shm_driver}", - "SHM_DRIVER=${shm_driver}", - "VIRTWL_DEVICE=${virtwl_device}", - "PEER_CMD_PREFIX=${peer_cmd_prefix}", - "FRAME_COLOR=${frame_color}", - "DARK_FRAME_COLOR=${dark_frame_color}", - ] + sources = [ "sommelier-main.cc" ] + + defines = sommelier_defines + + deps = [ ":libsommelier" ] } -executable("wayland_demo") { - pkg_deps = [ - "libbrillo-${libbase_ver}", - "libchrome-${libbase_ver}", - "wayland-client", - ] - libs = [ "wayland-client" ] - sources = [ - "demos/wayland_demo.cc", - ] +if (use.test) { + executable("sommelier_test") { + sources = [ "sommelier_test.cc" ] + + pkg_deps = [ "pixman-1" ] + + # gnlint: disable=GnLintCommonTesting + libs = [ + "gmock", + "gtest", + "pixman-1", + ] + defines = sommelier_defines + deps = [ ":libsommelier" ] + } } -executable("x11_demo") { - pkg_deps = [ - "libbrillo-${libbase_ver}", - "libchrome-${libbase_ver}", - ] - libs = [ "X11" ] - sources = [ - "demos/x11_demo.cc", - ] +if (use.fuzzer) { + executable("sommelier_wayland_fuzzer") { + sources = [ "sommelier-wayland-fuzzer.cc" ] + pkg_deps = [ "pixman-1" ] + libs = [ "pixman-1" ] + + configs += [ "//common-mk/common_fuzzer" ] + + defines = sommelier_defines + + deps = [ ":libsommelier" ] + } } diff --git a/sommelier/Makefile b/sommelier/Makefile deleted file mode 100644 index 4567aa8..0000000 --- a/sommelier/Makefile +++ /dev/null @@ -1,129 +0,0 @@ -CC=gcc -SED=sed -CLANG_FORMAT=clang-format-3.9 -CLANG_TIDY=clang-tidy-3.9 -PREFIX = /usr -SYSCONFDIR = /etc -BINDIR = $(PREFIX)/bin -SRCFILES := sommelier.c version.h -XMLFILES := protocol/aura-shell.xml protocol/viewporter.xml protocol/xdg-shell-unstable-v6.xml protocol/linux-dmabuf-unstable-v1.xml protocol/drm.xml protocol/keyboard-extension-unstable-v1.xml protocol/gtk-shell.xml protocol/relative-pointer-unstable-v1.xml protocol/text-input-unstable-v1.xml protocol/pointer-constraints-unstable-v1.xml -AUXFILES := Makefile README LICENSE AUTHORS sommelier@.service.in sommelier-x@.service.in sommelierrc sommelier.sh -ALLFILES := $(SRCFILES) $(XMLFILES) $(AUXFILES) -#GIT_VERSION := $(shell git describe --abbrev=4 --dirty --always --tags) -#DIST_VERSION := $(shell git describe --abbrev=0 --tags) -DIST_VERSION_BITS := $(subst ., ,$(DIST_VERSION)) -DIST_VERSION_MAJOR := $(word 1,$(DIST_VERSION_BITS)) -DIST_VERSION_MINOR := $(word 2,$(DIST_VERSION_BITS)) -DIST_VERSION_MINOR_NEXT := $(shell expr $(DIST_VERSION_MINOR) + 1) -CFLAGS=-g -Wall `pkg-config --cflags libdrm xcb xcb-composite xcb-xfixes wayland-server wayland-client gbm pixman-1` -I. -D_GNU_SOURCE=1 -DWL_HIDE_DEPRECATED=1 -DXWAYLAND_PATH=\"$(PREFIX)/bin/Xwayland\" -LDFLAGS=-lpthread -lm `pkg-config --libs libdrm xcb xcb-composite xcb-xfixes wayland-server wayland-client gbm pixman-1 xkbcommon` -DEPS = xdg-shell-unstable-v6-client-protocol.h xdg-shell-unstable-v6-server-protocol.h aura-shell-client-protocol.h viewporter-client-protocol.h viewporter-server-protocol.h linux-dmabuf-unstable-v1-client-protocol.h drm-server-protocol.h keyboard-extension-unstable-v1-client-protocol.h gtk-shell-server-protocol.h relative-pointer-unstable-v1-server-protocol.h relative-pointer-unstable-v1-client-protocol.h text-input-unstable-v1-client-protocol.h text-input-unstable-v1-server-protocol.h pointer-constraints-unstable-v1-client-protocol.h pointer-constraints-unstable-v1-server-protocol.h -OBJECTS = sommelier.o sommelier-compositor.o sommelier-data-device-manager.o sommelier-display.o sommelier-drm.o sommelier-gtk-shell.o sommelier-output.o sommelier-relative-pointer-manager.o sommelier-seat.o sommelier-shell.o sommelier-shm.o sommelier-subcompositor.o sommelier-text-input.o sommelier-viewporter.o sommelier-xdg-shell.o sommelier-pointer-constraints.o xdg-shell-unstable-v6-protocol.o aura-shell-protocol.o viewporter-protocol.o linux-dmabuf-unstable-v1-protocol.o drm-protocol.o keyboard-extension-unstable-v1-protocol.o gtk-shell-protocol.o relative-pointer-unstable-v1-protocol.o text-input-unstable-v1-protocol.o pointer-constraints-unstable-v1-protocol.o - -#all: sommelier sommelier@.service sommelier-x@.service -all: sommelier - -%.service: %.service.in - $(SED) \ - -e 's|@bindir[@]|$(BINDIR)|g' \ - -e 's|@sysconfdir[@]|$(SYSCONFDIR)|g' \ - -e 's|@version[@]|$(DIST_VERSION)|g' \ - $< > $@ - -sommelier: $(OBJECTS) - $(CC) $(OBJECTS) -o sommelier $(LDFLAGS) - strip -s sommelier - -%-protocol.c: protocol/%.xml - wayland-scanner private-code < $< > $@ - -%-client-protocol.h: protocol/%.xml - wayland-scanner client-header < $< > $@ - -%-server-protocol.h: protocol/%.xml - wayland-scanner server-header < $< > $@ - -%.o: %.c - $(CC) -c -o $@ $< $(CFLAGS) - -$(OBJECTS): $(DEPS) - -.PHONY: all install uninstall update-version dist deb version-clean clean style check-style tidy - -install: all - install -D sommelier \ - $(DESTDIR)$(PREFIX)/bin/sommelier - install -D sommelierrc $(DESTDIR)$(SYSCONFDIR)/sommelierrc - install -m 644 -D sommelier@.service \ - $(DESTDIR)$(PREFIX)/lib/systemd/user/sommelier@.service - install -m 644 -D sommelier-x@.service \ - $(DESTDIR)$(PREFIX)/lib/systemd/user/sommelier-x@.service - install -m 644 -D sommelier.sh $(DESTDIR)$(SYSCONFDIR)/profile.d/sommelier.sh - -uninstall: - rm -f $(DESTDIR)$(PREFIX)/bin/sommelier - rm -f $(DESTDIR)$(SYSCONFDIR)/sommelierrc - rm -f $(DESTDIR)$(PREFIX)/lib/systemd/user/sommelier@.service - rm -f $(DESTDIR)$(PREFIX)/lib/systemd/user/sommelier-x@.service - rm -f $(DESTDIR)$(SYSCONFDIR)/profile.d/sommelier.sh - -update-version: - dch -v $(DIST_VERSION_MAJOR).$(DIST_VERSION_MINOR_NEXT)-1 - git commit -m 'debian/changelog: bump to version $(DIST_VERSION_MAJOR).$(DIST_VERSION_MINOR_NEXT)' debian/changelog - $(SED) -i -e 's/VERSION "[0-9.]*"/VERSION "$(DIST_VERSION_MAJOR).$(DIST_VERSION_MINOR_NEXT)"/g' version.h - git tag $(DIST_VERSION_MAJOR).$(DIST_VERSION_MINOR_NEXT) - -dist: $(DEPS) - mkdir -p sommelier-$(DIST_VERSION) - cp -r $(ALLFILES) $(DEPS) debian sommelier-$(DIST_VERSION) - tar czf sommelier-$(DIST_VERSION).tar.gz sommelier-$(DIST_VERSION) - rm -rf sommelier-$(DIST_VERSION) - -deb: dist - ln -sf sommelier-$(DIST_VERSION).tar.gz sommelier_$(DIST_VERSION).orig.tar.gz - tar xzf sommelier-$(DIST_VERSION).tar.gz - cd sommelier-$(DIST_VERSION) && debuild -i -us -uc -b - rm -rf sommelier-$(DIST_VERSION) sommelier_$(DIST_VERSION).orig.tar.gz - -clean: - rm -f *~ *-protocol.c *-protocol.h *.o sommelier sommelier@.service \ - sommelier-x@.service sommelier-*.tar.gz sommelier*.deb \ - sommelier_*.build sommelier_*.buildinfo sommelier_*.changes - -style: $(DEPS) - @for src in $(SRCFILES) ; do \ - echo "Formatting $$src..."; \ - $(CLANG_FORMAT) -i "$$src"; \ - $(CLANG_TIDY) -checks='-*,readability-identifier-naming' \ - -config="{CheckOptions: [ \ - { key: readability-identifier-naming.StructCase, value: lower_case }, \ - { key: readability-identifier-naming.FunctionCase, value: lower_case }, \ - { key: readability-identifier-naming.VariableCase, value: lower_case }, \ - { key: readability-identifier-naming.GlobalConstantCase, value: lower_case }, \ - { key: readability-identifier-naming.EnumConstantCase, value: UPPER_CASE } \ - ]}" "$$src"; \ - done - @echo "Done" - -check-style: - @for src in $(SRCFILES) ; do \ - var=`$(CLANG_FORMAT) "$$src" | diff "$$src" - | wc -l`; \ - if [ $$var -ne 0 ] ; then \ - echo "$$src does not respect the coding style (diff: $$var lines)"; \ - exit 1; \ - fi; \ - done - @echo "Style check passed" - -tidy: $(DEPS) - @for src in $(SRCFILES); do \ - echo "Running tidy on $$src..."; \ - $(CLANG_TIDY) -checks="-*,modernize-use-auto,modernize-use-nullptr, \ - readability-else-after-return,readability-simplify-boolean-expr, \ - readability-redundant-member-init,modernize-use-default-member-init, \ - modernize-use-equals-default,modernize-use-equals-delete, \ - modernize-use-using,modernize-loop-convert, \ - cppcoreguidelines-no-malloc,misc-redundant-expression" \ - "$$src"; \ - done - @echo "Done" diff --git a/sommelier/OWNERS b/sommelier/OWNERS index e017d9c..af9efa2 100644 --- a/sommelier/OWNERS +++ b/sommelier/OWNERS @@ -1,5 +1,4 @@ -set noparent -reveman@chromium.org +cpelling@google.com +davidriley@chromium.org hollingum@google.com sidereal@google.com -davidriley@chromium.org diff --git a/sommelier/README.md b/sommelier/README.md index 82d9907..593dba0 100644 --- a/sommelier/README.md +++ b/sommelier/README.md @@ -5,14 +5,14 @@ compositing to a 'host' compositor. Sommelier includes a set of features that allows it to run inside a tight jail or virtual machine. Sommelier can run as service or as a wrapper around the execution of a -program. As a service, it spawns new processes as needed to service clients. -The parent process is called the master sommelier. +program. As a service, called the parent sommelier, it spawns new processes as +needed to service clients. ## Sommeliers -### Master Sommelier +### Parent Sommelier -The master sommelier instance will create a wayland socket in XDG_RUNTIME_DIR +The parent sommelier instance will create a wayland socket in XDG_RUNTIME_DIR and accept connections from regular wayland clients. Each connection will be serviced by spawning a child sommelier process. @@ -57,12 +57,6 @@ host compositor. Sommelier provides a shared memory driver option as a solution for this. What's the most appropriate option depends on the host compositor and device drivers available for allocating buffers. -### Noop - -The `noop` shared memory driver simply forwards buffers to the host without -any special processing. This requires that the client inside the container is -using memory that can be shared with the host compositor. - ### VirtWL The `virtwl` driver creates a set of intermediate virtwl buffers for each @@ -81,15 +75,6 @@ are: * Host compositor can avoid expensive texture uploads. * HW overlays can be used for presentation if support by the host compositor. -### DMABuf - -The `dmabuf` driver is similar to the `virtwl-dmabuf` driver. It creates a set -of intermediate buffers for each surface and copies minimal damaged areas from -the client’s standard shared memory buffer into the DMABuf buffer. However, -the buffer is allocated using a DRM device and a prime FD is used to access -buffer memory inside the container. Intermediate buffers are shared with the -host compositor using the linux_dmabuf protocol. - ## Damage Tracking Shared memory drivers that use intermediate buffers require some form of @@ -127,12 +112,6 @@ Socket pairs created inside a container cannot always be shared with the host compositor. Sommelier provides a data driver option as a solution for this. -### Noop - -The `noop` driver simply forwards socket pair FDs to the host without any -special processing. This requires that the client inside the container is -using socket pairs that can be shared with the host compositor. - ### VirtWL The `virtwl` driver creates a special pipe that can be shared with the host @@ -178,10 +157,11 @@ scaling used by sommelier and the host compositor. An exact value for DPI is calculated by sommelier. However, many Linux programs expect DPI to be one out of a well known set of values. Sommelier solves this by adjusting DPI using a set of buckets. For example, given the -default set of buckets (72, 96, 160, 240), Sommelier will use 96 as DPI when -the exact value is 112, or 160 when exact value is 188. The DPI buckets that +set of buckets (72, 96, 160, 240), Sommelier will use 96 as DPI when the +exact value is 112, or 160 when exact value is 188. The DPI buckets that sommelier should use can be specified with `--dpi=[DPI[,DPI...]]`. Where, -`--dpi=””` will result in sommelier exposing the exact DPI value to clients. +`--dpi=””` will result in sommelier exposing the exact DPI value to clients +(this is the default behaviour). ### XCursor @@ -194,7 +174,7 @@ If the host compositor support dynamic handling of keyboard events, then keyboard shortcuts are forwarded to the Linux program by default. A small set of shortcuts are expected to be reserved by the host compositor. A list of reserved shortcuts on Chrome OS can be found -[here](https://chromium.googlesource.com/chromium/src/+/master/ash/accelerators/accelerator_table.h#22). +[here](https://chromium.googlesource.com/chromium/src/+/HEAD/ash/accelerators/accelerator_table.h#22). There’s unfortunately no reliable way to detect if a Linux program handled a key event or not. This means that all non-reserved shortcuts that the user @@ -204,7 +184,7 @@ the "launcher" button during normal usage. The "launcher" button event is forwarded to Linux programs by default so it won’t work when a Linux program has keyboard focus unless this shortcut is explicitly listed as an accelerator. -Sommelier provides the `--accelerator=ACCELERATORS` flag for this purpose. +Sommelier provides the `--accelerators=ACCELERATORS` flag for this purpose. `ACCELERATORS` is a comma separated list of accelerators that shouldn’t be forwarded to the Linux program but instead handled by the host compositor. Each accelerator can contain a list of modifiers (e.g. ``) and @@ -214,15 +194,20 @@ above (which happens to have XKB keysym `Super_L` on the Chromebook Pixel), `--accelerators=Super_L` needs to be passed to sommelier for the this button to bring up the application launcher when Linux programs have keyboard focus. -Consistent with other flags, `SOMMELIER_ACCELERATORS` environment variable can +There is also the `--windowed-accelerators=WINDOWED_ACCELERATORS` flag for +accelerators that should be handled by the host compositor when the focused +window is windowed but not while it is fullscreen. + +Consistent with other flags, `SOMMELIER_ACCELERATORS` and +`SOMMELIER_WINDOWED_ACCELERATORS` environment variable can be used as an alternative to the command line flag. ## Examples -Start master sommelier and use wayland-1 as name of socket to listen on: +Start parent sommelier and use wayland-1 as name of socket to listen on: ``` -sommelier --master --socket=wayland-1 +sommelier --parent --socket=wayland-1 ``` Start sommelier that runs weston-terminal with density scale multiplier 1.5: diff --git a/sommelier/build/args.gn b/sommelier/build/args.gn deleted file mode 100644 index 5fc774c..0000000 --- a/sommelier/build/args.gn +++ /dev/null @@ -1,8 +0,0 @@ -# Set build arguments here. See `gn buildargs`. -pkg_config = "/usr/bin/pkg-config" -libdir = "/usr/lib/x86_64-linux-gnu/" -platform_subdir="vm_tools/sommelier" -cxx="g++" -cc="gcc" -ar="/usr/bin/ar" -#use.amd64=true diff --git a/sommelier/compositor/OWNERS b/sommelier/compositor/OWNERS new file mode 100644 index 0000000..95fde25 --- /dev/null +++ b/sommelier/compositor/OWNERS @@ -0,0 +1,2 @@ +ryanneph@google.com +zzyiwei@google.com diff --git a/sommelier/compositor/sommelier-compositor.cc b/sommelier/compositor/sommelier-compositor.cc new file mode 100644 index 0000000..2e64bcc --- /dev/null +++ b/sommelier/compositor/sommelier-compositor.cc @@ -0,0 +1,1138 @@ +// Copyright 2018 The ChromiumOS Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "../sommelier.h" // NOLINT(build/include_directory) +#include "../sommelier-timing.h" // NOLINT(build/include_directory) +#include "../sommelier-tracing.h" // NOLINT(build/include_directory) +#include "../sommelier-transform.h" // NOLINT(build/include_directory) +#include "../sommelier-xshape.h" // NOLINT(build/include_directory) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "drm-server-protocol.h" // NOLINT(build/include_directory) +#include "linux-dmabuf-unstable-v1-client-protocol.h" // NOLINT(build/include_directory) +#include "linux-explicit-synchronization-unstable-v1-client-protocol.h" // NOLINT(build/include_directory) +#include "viewporter-client-protocol.h" // NOLINT(build/include_directory) + +#define DMA_BUF_SYNC_READ (1 << 0) +#define DMA_BUF_SYNC_WRITE (2 << 0) +#define DMA_BUF_SYNC_RW (DMA_BUF_SYNC_READ | DMA_BUF_SYNC_WRITE) +#define DMA_BUF_SYNC_START (0 << 2) +#define DMA_BUF_SYNC_END (1 << 2) + +struct dma_buf_sync_file { + __u32 flags; + __s32 fd; +}; + +#define DMA_BUF_BASE 'b' +#define DMA_BUF_IOCTL_SYNC _IOW(DMA_BUF_BASE, 0, struct dma_buf_sync) +// TODO(b/189505947): DMA_BUF_IOCTL_EXPORT_SYNC_FILE might not exist, and +// hasn't been upstreamed. Remove this comment when the ioctl has landed. +#define DMA_BUF_IOCTL_EXPORT_SYNC_FILE \ + _IOWR(DMA_BUF_BASE, 2, struct dma_buf_sync_file) + +struct sl_host_compositor { + struct sl_compositor* compositor; + struct wl_resource* resource; + struct wl_compositor* proxy; +}; + +struct sl_output_buffer { + struct wl_list link; + uint32_t width; + uint32_t height; + uint32_t format; + struct wl_buffer* internal; + struct sl_mmap* mmap; + struct pixman_region32 surface_damage; + struct pixman_region32 buffer_damage; + pixman_image_t* shape_image; + struct sl_host_surface* surface; +}; + +static void sl_virtwl_dmabuf_sync(int fd, __u32 flags, struct sl_context* ctx) { + int rv; + rv = ctx->channel->sync(fd, flags); + assert(!rv); + UNUSED(rv); +} + +static void sl_virtwl_dmabuf_begin_write(int fd, struct sl_context* ctx) { + sl_virtwl_dmabuf_sync(fd, DMA_BUF_SYNC_START | DMA_BUF_SYNC_WRITE, ctx); +} + +static void sl_virtwl_dmabuf_end_write(int fd, struct sl_context* ctx) { + sl_virtwl_dmabuf_sync(fd, DMA_BUF_SYNC_END | DMA_BUF_SYNC_WRITE, ctx); +} + +static void sl_output_buffer_destroy(struct sl_output_buffer* buffer) { + wl_buffer_destroy(buffer->internal); + sl_mmap_unref(buffer->mmap); + pixman_region32_fini(&buffer->surface_damage); + pixman_region32_fini(&buffer->buffer_damage); + wl_list_remove(&buffer->link); + + if (buffer->shape_image) + pixman_image_unref(buffer->shape_image); + + delete buffer; +} + +static uint32_t try_wl_resource_get_id(wl_resource* resource) { + return resource ? wl_resource_get_id(resource) : -1; +} + +static void sl_output_buffer_release(void* data, struct wl_buffer* buffer) { + struct sl_output_buffer* output_buffer = + static_cast(wl_buffer_get_user_data(buffer)); + TRACE_EVENT("surface", "sl_output_buffer_release", "resource_id", + try_wl_resource_get_id(output_buffer->surface->resource)); + struct sl_host_surface* host_surface = output_buffer->surface; + + wl_list_remove(&output_buffer->link); + wl_list_insert(&host_surface->released_buffers, &output_buffer->link); +} + +static const struct wl_buffer_listener sl_output_buffer_listener = { + sl_output_buffer_release}; + +static void sl_host_surface_destroy(struct wl_client* client, + struct wl_resource* resource) { + TRACE_EVENT("surface", "sl_host_surface_destroy", "resource_id", + try_wl_resource_get_id(resource)); + wl_resource_destroy(resource); +} + +static void sl_host_surface_attach(struct wl_client* client, + struct wl_resource* resource, + struct wl_resource* buffer_resource, + int32_t x, + int32_t y) { + auto resource_id = wl_resource_get_id(resource); + auto buffer_id = + buffer_resource ? wl_resource_get_id(buffer_resource) : kUnknownBufferId; + TRACE_EVENT("surface", "sl_host_surface_attach", "resource_id", resource_id, + "buffer_id", buffer_id); + struct sl_host_surface* host = + static_cast(wl_resource_get_user_data(resource)); + if (host->ctx->timing != NULL) { + host->ctx->timing->UpdateLastAttach(resource_id, buffer_id); + } + struct sl_host_buffer* host_buffer = + buffer_resource ? static_cast( + wl_resource_get_user_data(buffer_resource)) + : NULL; + struct wl_buffer* buffer_proxy = NULL; + struct sl_window* window = NULL; + bool window_shaped = false; + + // The window_shaped flag should only be set if the xshape functionality + // has been explicitly enabled + window = sl_context_lookup_window_for_surface(host->ctx, resource); + if (window && host->ctx->enable_xshape) + window_shaped = window->shaped; + + host->current_buffer = NULL; + if (host->contents_shm_mmap) { + sl_mmap_unref(host->contents_shm_mmap); + host->contents_shm_mmap = NULL; + } + + if (host_buffer) { + host->contents_width = host_buffer->width; + host->contents_height = host_buffer->height; + host->contents_shm_format = host_buffer->shm_format; + host->proxy_buffer = host_buffer->proxy; + buffer_proxy = host_buffer->proxy; + + if (!host_buffer->is_drm) { + if (host_buffer->shm_mmap) + host->contents_shm_mmap = sl_mmap_ref(host_buffer->shm_mmap); + } else { + if (window_shaped && host_buffer->shm_mmap) { + host->contents_shm_mmap = sl_mmap_ref(host_buffer->shm_mmap); + } else { + // A fallback if for some reason the DRM PRIME mmap container was + // not created (or if this is not a shaped window) + host->contents_shm_mmap = NULL; + window_shaped = false; + } + } + } + + // Transfer the flag and shape data over to the surface + // if we are working on a shaped window + if (window_shaped) { + host->contents_shaped = true; + pixman_region32_copy(&host->contents_shape, &window->shape_rectangles); + } else { + host->contents_shaped = false; + pixman_region32_clear(&host->contents_shape); + } + + // This code will also check if we need to reallocate + // a output_surface based on the status of the shaped flag + // + // An output_surface that is shaped will have its format + // forced to ARGB8888 (hence the changes below) + if (host->contents_shm_mmap) { + while (!wl_list_empty(&host->released_buffers)) { + host->current_buffer = wl_container_of(host->released_buffers.next, + host->current_buffer, link); + + if (host->current_buffer->width == host_buffer->width && + host->current_buffer->height == host_buffer->height && + ((window_shaped && host->current_buffer->shape_image && + host->current_buffer->format == WL_SHM_FORMAT_ARGB8888) || + (!window_shaped && + host->current_buffer->format == host_buffer->shm_format))) { + break; + } + + sl_output_buffer_destroy(host->current_buffer); + host->current_buffer = NULL; + } + + // Allocate new output buffer. + if (!host->current_buffer) { + TRACE_EVENT("surface", "sl_host_surface_attach: allocate_buffer", + "dmabuf_enabled", host->ctx->channel->supports_dmabuf()); + size_t width = host_buffer->width; + size_t height = host_buffer->height; + uint32_t shm_format = + window_shaped ? WL_SHM_FORMAT_ARGB8888 : host_buffer->shm_format; + size_t bpp = sl_shm_bpp_for_shm_format(shm_format); + size_t num_planes = sl_shm_num_planes_for_shm_format(shm_format); + + host->current_buffer = new sl_output_buffer(); + wl_list_insert(&host->released_buffers, &host->current_buffer->link); + host->current_buffer->width = width; + host->current_buffer->height = height; + host->current_buffer->format = shm_format; + host->current_buffer->surface = host; + pixman_region32_init_rect(&host->current_buffer->surface_damage, 0, 0, + MAX_SIZE, MAX_SIZE); + pixman_region32_init_rect(&host->current_buffer->buffer_damage, 0, 0, + MAX_SIZE, MAX_SIZE); + + if (window_shaped) { + host->current_buffer->shape_image = pixman_image_create_bits_no_clear( + PIXMAN_a8r8g8b8, width, height, NULL, 0); + + assert(host->current_buffer->shape_image); + } else { + host->current_buffer->shape_image = NULL; + } + + if (host->ctx->channel->supports_dmabuf()) { + int rv; + size_t size; + struct zwp_linux_buffer_params_v1* buffer_params; + struct WaylandBufferCreateInfo create_info = {0}; + struct WaylandBufferCreateOutput create_output = {0}; + create_info.dmabuf = true; + + create_info.width = static_cast<__u32>(width); + create_info.height = static_cast<__u32>(height); + create_info.drm_format = sl_drm_format_for_shm_format(shm_format); + + rv = host->ctx->channel->allocate(create_info, create_output); + if (rv) { + fprintf(stderr, "error: virtwl dmabuf allocation failed: %s\n", + strerror(-rv)); + _exit(EXIT_FAILURE); + } + + size = create_output.host_size; + buffer_params = zwp_linux_dmabuf_v1_create_params( + host->ctx->linux_dmabuf->internal); + zwp_linux_buffer_params_v1_add(buffer_params, create_output.fd, 0, + create_output.offsets[0], + create_output.strides[0], 0, 0); + if (num_planes > 1) { + zwp_linux_buffer_params_v1_add(buffer_params, create_output.fd, 1, + create_output.offsets[1], + create_output.strides[1], 0, 0); + size = MAX(size, create_output.offsets[1] + + create_output.offsets[1] * height / + host->contents_shm_mmap->y_ss[1]); + } + host->current_buffer->internal = + zwp_linux_buffer_params_v1_create_immed( + buffer_params, width, height, create_info.drm_format, 0); + zwp_linux_buffer_params_v1_destroy(buffer_params); + + host->current_buffer->mmap = sl_mmap_create( + create_output.fd, size, bpp, num_planes, create_output.offsets[0], + create_output.strides[0], create_output.offsets[1], + create_output.strides[1], host->contents_shm_mmap->y_ss[0], + host->contents_shm_mmap->y_ss[1]); + host->current_buffer->mmap->begin_write = sl_virtwl_dmabuf_begin_write; + host->current_buffer->mmap->end_write = sl_virtwl_dmabuf_end_write; + } else { + size_t size = host->contents_shm_mmap->size; + struct WaylandBufferCreateInfo create_info = {0}; + struct WaylandBufferCreateOutput create_output = {0}; + struct wl_shm_pool* pool; + int rv; + + create_info.drm_format = DRM_FORMAT_R8; + create_info.height = 1; + create_info.width = size; + create_info.size = static_cast<__u32>(size); + + rv = host->ctx->channel->allocate(create_info, create_output); + UNUSED(rv); + + pool = wl_shm_create_pool(host->ctx->shm->internal, create_output.fd, + create_output.host_size); + + host->current_buffer->internal = wl_shm_pool_create_buffer( + pool, 0, width, height, host->contents_shm_mmap->stride[0], + shm_format); + wl_shm_pool_destroy(pool); + + host->current_buffer->mmap = sl_mmap_create( + create_output.fd, create_output.host_size, bpp, num_planes, 0, + host->contents_shm_mmap->stride[0], + host->contents_shm_mmap->offset[1] - + host->contents_shm_mmap->offset[0], + host->contents_shm_mmap->stride[1], + host->contents_shm_mmap->y_ss[0], host->contents_shm_mmap->y_ss[1]); + } + + assert(host->current_buffer->internal); + assert(host->current_buffer->mmap); + + wl_buffer_add_listener(host->current_buffer->internal, + &sl_output_buffer_listener, host->current_buffer); + } + } + + sl_transform_guest_to_host(host->ctx, host, &x, &y); + + // Save these values in case we need to roll back the decisions + // made here in the commit phase of the attach-commit cycle + host->contents_x_offset = x; + host->contents_y_offset = y; + + if (host_buffer && host_buffer->sync_point) { + TRACE_EVENT("surface", "sl_host_surface_attach: sync_point"); + dma_buf_sync_file sync_file; + + bool needs_sync = true; + if (host->surface_sync) { + int ret = 0; + sync_file.flags = DMA_BUF_SYNC_READ; + do { + ret = ioctl(host_buffer->sync_point->fd, DMA_BUF_IOCTL_EXPORT_SYNC_FILE, + &sync_file); + } while (ret == -1 && (errno == EAGAIN || errno == EINTR)); + + if (!ret) { + zwp_linux_surface_synchronization_v1_set_acquire_fence( + host->surface_sync, sync_file.fd); + close(sync_file.fd); + needs_sync = false; + } else if (ret == -1 && errno == ENOTTY) { + // export sync file ioctl not implemented. Revert to previous method of + // guest side sync going forward. + zwp_linux_surface_synchronization_v1_destroy(host->surface_sync); + host->surface_sync = NULL; + fprintf(stderr, + "DMA_BUF_IOCTL_EXPORT_SYNC_FILE not implemented, defaulting " + "to implicit fence for synchronization.\n"); + } else { + fprintf(stderr, + "Explicit synchronization failed with reason: %s. " + "Will retry on next attach.\n", + strerror(errno)); + } + } + + if (needs_sync) { + host_buffer->sync_point->sync(host->ctx, host_buffer->sync_point); + } + } + + if (host->current_buffer) { + assert(host->current_buffer->internal); + wl_surface_attach(host->proxy, host->current_buffer->internal, x, y); + } else { + wl_surface_attach(host->proxy, buffer_proxy, x, y); + } + + wl_list_for_each(window, &host->ctx->windows, link) { + if (window->host_surface_id == try_wl_resource_get_id(resource)) { + while (sl_process_pending_configure_acks(window, host)) + continue; + + break; + } + } +} // NOLINT(whitespace/indent) + +// Return the scale and offset from surface coordinates to buffer pixel +// coordinates, taking the viewport into account (if any). +void compute_buffer_scale_and_offset(const sl_host_surface* host, + const sl_viewport* viewport, + double* out_scale_x, + double* out_scale_y, + wl_fixed_t* out_offset_x, + wl_fixed_t* out_offset_y) { + double scale_x = host->contents_scale; + double scale_y = host->contents_scale; + wl_fixed_t offset_x = 0; + wl_fixed_t offset_y = 0; + if (viewport) { + double contents_width = host->contents_width; + double contents_height = host->contents_height; + + if (viewport->src_x >= 0 && viewport->src_y >= 0) { + offset_x = viewport->src_x; + offset_y = viewport->src_y; + } + + if (viewport->dst_width > 0 && viewport->dst_height > 0) { + scale_x *= contents_width / viewport->dst_width; + scale_y *= contents_height / viewport->dst_height; + + // Take source rectangle into account when both destination size and + // source rectangle are set. If only source rectangle is set, then + // it determines the surface size so it can be ignored. + if (viewport->src_width >= 0 && viewport->src_height >= 0) { + scale_x *= wl_fixed_to_double(viewport->src_width) / contents_width; + scale_y *= wl_fixed_to_double(viewport->src_height) / contents_height; + } + } + } + *out_scale_x = scale_x; + *out_scale_y = scale_y; + *out_offset_x = offset_x; + *out_offset_y = offset_y; +} + +static void sl_host_surface_damage(struct wl_client* client, + struct wl_resource* resource, + int32_t x, + int32_t y, + int32_t width, + int32_t height) { + TRACE_EVENT("surface", "sl_host_surface_damage", "resource_id", + try_wl_resource_get_id(resource)); + const struct sl_host_surface* host = + static_cast(wl_resource_get_user_data(resource)); + + struct sl_output_buffer* buffer; + int64_t x1, y1, x2, y2; + + wl_list_for_each(buffer, &host->busy_buffers, link) { + pixman_region32_union_rect(&buffer->surface_damage, &buffer->surface_damage, + x, y, width, height); + } + wl_list_for_each(buffer, &host->released_buffers, link) { + pixman_region32_union_rect(&buffer->surface_damage, &buffer->surface_damage, + x, y, width, height); + } + + x1 = x; + y1 = y; + x2 = x1 + width; + y2 = y1 + height; + + sl_transform_damage_coord(host->ctx, host, 1.0, 1.0, &x1, &y1, &x2, &y2); + wl_surface_damage(host->proxy, x1, y1, x2 - x1, y2 - y1); +} + +static void sl_host_surface_damage_buffer(struct wl_client* client, + struct wl_resource* resource, + int32_t x, + int32_t y, + int32_t width, + int32_t height) { + TRACE_EVENT("surface", "sl_host_surface_damage_buffer", "resource_id", + try_wl_resource_get_id(resource)); + const struct sl_host_surface* host = + static_cast(wl_resource_get_user_data(resource)); + struct sl_output_buffer* buffer; + + wl_list_for_each(buffer, &host->busy_buffers, link) { + pixman_region32_union_rect(&buffer->buffer_damage, &buffer->buffer_damage, + x, y, width, height); + } + wl_list_for_each(buffer, &host->released_buffers, link) { + pixman_region32_union_rect(&buffer->buffer_damage, &buffer->buffer_damage, + x, y, width, height); + } + + // Forward wl_surface_damage() call to the host. Since the damage region is + // given in buffer pixel coordinates, convert to surface coordinates first. + // If the host supports wl_surface_damage_buffer one day, we can avoid this + // conversion. + double scale_x, scale_y; + wl_fixed_t offset_x, offset_y; + struct sl_viewport* viewport = NULL; + if (!wl_list_empty(&host->contents_viewport)) + viewport = wl_container_of(host->contents_viewport.next, viewport, link); + + compute_buffer_scale_and_offset(host, viewport, &scale_x, &scale_y, &offset_x, + &offset_y); + + int64_t x1 = x - wl_fixed_to_int(offset_x); + int64_t y1 = y - wl_fixed_to_int(offset_y); + int64_t x2 = x1 + width; + int64_t y2 = y1 + height; + + sl_transform_damage_coord(host->ctx, host, scale_x, scale_y, &x1, &y1, &x2, + &y2); + wl_surface_damage(host->proxy, x1, y1, x2 - x1, y2 - y1); +} + +static void sl_frame_callback_done(void* data, + struct wl_callback* callback, + uint32_t time) { + TRACE_EVENT("surface", "sl_frame_callback_done"); + struct sl_host_callback* host = + static_cast(wl_callback_get_user_data(callback)); + + wl_callback_send_done(host->resource, time); + wl_resource_destroy(host->resource); +} + +static const struct wl_callback_listener sl_frame_callback_listener = { + sl_frame_callback_done}; + +static void sl_host_callback_destroy(struct wl_resource* resource) { + TRACE_EVENT("surface", "sl_host_callback_destroy"); + struct sl_host_callback* host = + static_cast(wl_resource_get_user_data(resource)); + + wl_callback_destroy(host->proxy); + wl_resource_set_user_data(resource, NULL); + delete host; +} + +static void sl_host_surface_frame(struct wl_client* client, + struct wl_resource* resource, + uint32_t callback) { + TRACE_EVENT("surface", "sl_host_surface_frame", "resource_id", + try_wl_resource_get_id(resource)); + struct sl_host_surface* host = + static_cast(wl_resource_get_user_data(resource)); + struct sl_host_callback* host_callback = new sl_host_callback(); + + host_callback->resource = + wl_resource_create(client, &wl_callback_interface, 1, callback); + wl_resource_set_implementation(host_callback->resource, NULL, host_callback, + sl_host_callback_destroy); + host_callback->proxy = wl_surface_frame(host->proxy); + wl_callback_add_listener(host_callback->proxy, &sl_frame_callback_listener, + host_callback); +} + +static void copy_damaged_rect(sl_host_surface* host, + pixman_box32_t* rect, + bool shaped, + double scale_x, + double scale_y, + double offset_x, + double offset_y) { + uint8_t* src_addr = static_cast(host->contents_shm_mmap->addr); + uint8_t* dst_addr = static_cast(host->current_buffer->mmap->addr); + size_t* src_offset = host->contents_shm_mmap->offset; + size_t* dst_offset = host->current_buffer->mmap->offset; + size_t* src_stride = host->contents_shm_mmap->stride; + size_t* dst_stride = host->current_buffer->mmap->stride; + size_t* y_ss = host->contents_shm_mmap->y_ss; + size_t bpp = host->contents_shm_mmap->bpp; + size_t num_planes = host->contents_shm_mmap->num_planes; + int32_t x1, y1, x2, y2; + size_t null_set[3] = {0, 0, 0}; + size_t shape_stride[3] = {0, 0, 0}; + + if (shaped) { + // If we are copying from a shaped window, the actual image data + // we want comes from the shape_image. We are making the modifications + // here so the source data comes from this buffer. + src_addr = reinterpret_cast( + pixman_image_get_data(host->current_buffer->shape_image)); + src_offset = null_set; + src_stride = shape_stride; + + shape_stride[0] = + pixman_image_get_stride(host->current_buffer->shape_image); + } + + // Enclosing rect after applying scale and offset. + x1 = rect->x1 * scale_x + offset_x; + y1 = rect->y1 * scale_y + offset_y; + x2 = rect->x2 * scale_x + offset_x + 0.5; + y2 = rect->y2 * scale_y + offset_y + 0.5; + + x1 = MAX(0, x1); + y1 = MAX(0, y1); + x2 = MIN(static_cast(host->contents_width), x2); + y2 = MIN(static_cast(host->contents_height), y2); + + if (x1 < x2 && y1 < y2) { + size_t i; + + for (i = 0; i < num_planes; ++i) { + uint8_t* src_base = src_addr + src_offset[i]; + uint8_t* dst_base = dst_addr + dst_offset[i]; + uint8_t* src = src_base + y1 * src_stride[i] + x1 * bpp; + uint8_t* dst = dst_base + y1 * dst_stride[i] + x1 * bpp; + int32_t width = x2 - x1; + int32_t height = (y2 - y1) / y_ss[i]; + size_t bytes = width * bpp; + + while (height--) { + memcpy(dst, src, bytes); + dst += dst_stride[i]; + src += src_stride[i]; + } + } + } +} + +static void sl_host_surface_commit(struct wl_client* client, + struct wl_resource* resource) { + auto resource_id = try_wl_resource_get_id(resource); + TRACE_EVENT( + "surface", "sl_host_surface_commit", "resource_id", resource_id, + [&](perfetto::EventContext p) { perfetto_annotate_time_sync(p); }); + struct sl_host_surface* host = + static_cast(wl_resource_get_user_data(resource)); + if (host->ctx->timing != NULL) { + host->ctx->timing->UpdateLastCommit(resource_id); + } + struct sl_viewport* viewport = NULL; + + if (!wl_list_empty(&host->contents_viewport)) + viewport = wl_container_of(host->contents_viewport.next, viewport, link); + + // We are doing this check outside the contents_shm_mmap check below so + // we can bail out on the shaped processing of a DRM buffer in case mapping + // it fails. + if (host->contents_shm_mmap) { + bool mmap_ensured = sl_mmap_begin_access(host->contents_shm_mmap); + + if (!mmap_ensured) { + // Clean up anything left from begin_access + sl_mmap_end_access(host->contents_shm_mmap); + + if (host->contents_shaped) { + // Shaped contents, with a source buffer. + // We need to fallback and disable shape processing + // This might also be a good event to log somewhere + sl_mmap_unref(host->contents_shm_mmap); + + // Release the allocated output buffer back to the queue + host->contents_shm_mmap = NULL; + host->contents_shaped = false; + pixman_region32_clear(&host->contents_shape); + sl_output_buffer_release(NULL, host->current_buffer->internal); + + // Attach the original buffer back, ensure proxy_buffer is not NULL + assert(host->proxy_buffer); + wl_surface_attach(host->proxy, host->proxy_buffer, + host->contents_x_offset, host->contents_y_offset); + } else { + // If we are not shaped, we will still need access to the buffer in this + // case. We shouldn't get here. We are using this assert to provide some + // clues within error logs that may result from hitting this point. + assert(mmap_ensured); + } + } else { + // In this case, perform the image manipulation if we are dealing + // with a shaped surface. + if (host->contents_shaped) + sl_xshape_generate_argb_image( + host->ctx, &host->contents_shape, host->contents_shm_mmap, + host->current_buffer->shape_image, host->contents_shm_format); + } + } + + if (host->contents_shm_mmap) { + double contents_scale_x, contents_scale_y; + wl_fixed_t contents_offset_x, contents_offset_y; + compute_buffer_scale_and_offset(host, viewport, &contents_scale_x, + &contents_scale_y, &contents_offset_x, + &contents_offset_y); + + if (host->current_buffer->mmap->begin_write) + host->current_buffer->mmap->begin_write(host->current_buffer->mmap->fd, + host->ctx); + + // Copy damaged regions (surface-relative coordinates). + int n; + pixman_box32_t* rect = + pixman_region32_rectangles(&host->current_buffer->surface_damage, &n); + while (n--) { + TRACE_EVENT("surface", + "sl_host_surface_commit: memcpy_loop (surface damage)"); + copy_damaged_rect(host, rect, host->contents_shaped, contents_scale_x, + contents_scale_y, wl_fixed_to_double(contents_offset_x), + wl_fixed_to_double(contents_offset_y)); + ++rect; + } + + // Copy damaged regions (buffer-relative coordinates). + // + // In theory, if we've accumulated both surface damage and buffer damage, + // it might be more efficient to first transform and union the regions, so + // that we won't ever copy the same pixel twice. + // In practice, wl_surface::damage_buffer obsoletes wl_surface::damage, and + // it doesn't seem worthwhile to optimize for the edge case in which an app + // uses both in the same frame. + rect = pixman_region32_rectangles(&host->current_buffer->buffer_damage, &n); + while (n--) { + TRACE_EVENT("surface", + "sl_host_surface_commit: memcpy_loop (buffer damage)"); + copy_damaged_rect(host, rect, host->contents_shaped, 1.0, 1.0, 0.0, 0.0); + ++rect; + } + + if (host->current_buffer->mmap->end_write) + host->current_buffer->mmap->end_write(host->current_buffer->mmap->fd, + host->ctx); + + pixman_region32_clear(&host->current_buffer->surface_damage); + pixman_region32_clear(&host->current_buffer->buffer_damage); + + wl_list_remove(&host->current_buffer->link); + wl_list_insert(&host->busy_buffers, &host->current_buffer->link); + } + + if (host->contents_width && host->contents_height) { + double scale = host->ctx->scale * host->contents_scale; + + if (host->viewport) { + int width = host->contents_width; + int height = host->contents_height; + + // We need to take the client's viewport into account while still + // making sure our scale is accounted for. + if (viewport) { + if (viewport->src_x >= 0 && viewport->src_y >= 0 && + viewport->src_width >= 0 && viewport->src_height >= 0) { + wp_viewport_set_source(host->viewport, viewport->src_x, + viewport->src_y, viewport->src_width, + viewport->src_height); + + // If the source rectangle is set and the destination size is not + // set, then src_width and src_height should be integers, and the + // surface size becomes the source rectangle size. + width = wl_fixed_to_int(viewport->src_width); + height = wl_fixed_to_int(viewport->src_height); + } + + // Use destination size as surface size when set. + if (viewport->dst_width >= 0 && viewport->dst_height >= 0) { + width = viewport->dst_width; + height = viewport->dst_height; + } + } + + int32_t vp_width = width; + int32_t vp_height = height; + + // Consult with the transform function to see if the + // viewport destination set is necessary + if (sl_transform_viewport_scale(host->ctx, host, host->contents_scale, + &vp_width, &vp_height)) { + wp_viewport_set_destination(host->viewport, vp_width, vp_height); + } + } else { + wl_surface_set_buffer_scale(host->proxy, scale); + } + } + + // No need to defer client commits if surface has a role. E.g. is a cursor + // or shell surface. + if (host->has_role) { + TRACE_EVENT("surface", "sl_host_surface_commit: wl_surface_commit", + "resource_id", resource_id, "has_role", host->has_role); + wl_surface_commit(host->proxy); + + // GTK determines the scale based on the output the surface has entered. + // If the surface has not entered any output, then have it enter the + // internal output. TODO(reveman): Remove this when surface-output tracking + // has been implemented in Chrome. + if (!host->has_output) { + struct sl_host_output* output; + + wl_list_for_each(output, &host->ctx->host_outputs, link) { + if (output->internal) { + wl_surface_send_enter(host->resource, output->resource); + host->has_output = 1; + break; + } + } + } + } else { + TRACE_EVENT("surface", "sl_host_surface_commit: wl_surface_commit", + "resource_id", resource_id, "has_role", host->has_role); + // Commit if surface is associated with a window. Otherwise, defer + // commit until window is created. + struct sl_window* window; + wl_list_for_each(window, &host->ctx->windows, link) { + if (window->host_surface_id == try_wl_resource_get_id(resource)) { + if (window->xdg_surface) { + wl_surface_commit(host->proxy); + if (host->contents_width && host->contents_height) + window->realized = 1; + } + break; + } + } + } + + if (host->contents_shm_mmap) { + if (host->contents_shm_mmap->buffer_resource) { + wl_buffer_send_release(host->contents_shm_mmap->buffer_resource); + } + sl_mmap_end_access(host->contents_shm_mmap); + sl_mmap_unref(host->contents_shm_mmap); + host->contents_shm_mmap = NULL; + } +} + +static void sl_host_surface_set_buffer_scale(struct wl_client* client, + struct wl_resource* resource, + int32_t scale) { + struct sl_host_surface* host = + static_cast(wl_resource_get_user_data(resource)); + + host->contents_scale = scale; +} + +static const struct wl_surface_interface sl_surface_implementation = { + sl_host_surface_destroy, + sl_host_surface_attach, + sl_host_surface_damage, + sl_host_surface_frame, + ForwardRequest, + ForwardRequest, + sl_host_surface_commit, + ForwardRequest, + sl_host_surface_set_buffer_scale, + sl_host_surface_damage_buffer}; + +static void sl_destroy_host_surface(struct wl_resource* resource) { + TRACE_EVENT("surface", "sl_destroy_host_surface", "resource_id", + try_wl_resource_get_id(resource)); + struct sl_host_surface* host = + static_cast(wl_resource_get_user_data(resource)); + struct sl_window *window, *surface_window = NULL; + struct sl_output_buffer* buffer; + + wl_list_for_each(window, &host->ctx->windows, link) { + if (window->host_surface_id == try_wl_resource_get_id(resource)) { + surface_window = window; + break; + } + } + + if (surface_window) { + surface_window->host_surface_id = 0; + sl_window_update(surface_window); + } + + if (host->contents_shm_mmap) + sl_mmap_unref(host->contents_shm_mmap); + + while (!wl_list_empty(&host->released_buffers)) { + buffer = wl_container_of(host->released_buffers.next, buffer, link); + sl_output_buffer_destroy(buffer); + } + while (!wl_list_empty(&host->busy_buffers)) { + buffer = wl_container_of(host->busy_buffers.next, buffer, link); + sl_output_buffer_destroy(buffer); + } + while (!wl_list_empty(&host->contents_viewport)) + wl_list_remove(host->contents_viewport.next); + + if (host->viewport) + wp_viewport_destroy(host->viewport); + wl_surface_destroy(host->proxy); + wl_resource_set_user_data(resource, NULL); + if (host->surface_sync) { + zwp_linux_surface_synchronization_v1_destroy(host->surface_sync); + host->surface_sync = NULL; + } + + pixman_region32_fini(&host->contents_shape); + delete host; +} + +static void sl_surface_enter(void* data, + struct wl_surface* surface, + struct wl_output* output) { + TRACE_EVENT("surface", "sl_surface_enter"); + struct sl_host_surface* host = + static_cast(wl_surface_get_user_data(surface)); + struct sl_host_output* host_output = + static_cast(wl_output_get_user_data(output)); + + wl_surface_send_enter(host->resource, host_output->resource); + host->has_output = 1; + host->output = host_output; + sl_transform_reset_surface_scale(host->ctx, host); + struct sl_window* window = NULL; + window = sl_context_lookup_window_for_surface(host->ctx, host->resource); + if (window) { + sl_transform_try_window_scale(host->ctx, host, window->width, + window->height); + sl_window_update(window); + } +} + +static void sl_surface_leave(void* data, + struct wl_surface* surface, + struct wl_output* output) { + TRACE_EVENT("surface", "sl_surface_leave"); + struct sl_host_surface* host = + static_cast(wl_surface_get_user_data(surface)); + struct sl_host_output* host_output = + static_cast(wl_output_get_user_data(output)); + + wl_surface_send_leave(host->resource, host_output->resource); + host->has_output = 0; + host->output.Reset(); +} + +static const struct wl_surface_listener sl_surface_listener = { + sl_surface_enter, sl_surface_leave}; + +static void sl_region_destroy(struct wl_client* client, + struct wl_resource* resource) { + wl_resource_destroy(resource); +} + +static void sl_region_add(struct wl_client* client, + struct wl_resource* resource, + int32_t x, + int32_t y, + int32_t width, + int32_t height) { + struct sl_host_region* host = + static_cast(wl_resource_get_user_data(resource)); + int32_t x1 = x; + int32_t y1 = y; + int32_t x2 = x + width; + int32_t y2 = y + height; + + // A region object isn't attached to a surface, so + // we will not use any surface specific scaling factors here + // + // TODO(mrisaacb): See if the effect of applying surface specific + // scaling values to region objects is noticeable. Interjecting + // here would require caching the object within Sommelier and + // transferring the modified coordinates over when referenced. + + sl_transform_guest_to_host(host->ctx, nullptr, &x1, &y1); + sl_transform_guest_to_host(host->ctx, nullptr, &x2, &y2); + + wl_region_add(host->proxy, x1, y1, x2 - x1, y2 - y1); +} + +static void sl_region_subtract(struct wl_client* client, + struct wl_resource* resource, + int32_t x, + int32_t y, + int32_t width, + int32_t height) { + struct sl_host_region* host = + static_cast(wl_resource_get_user_data(resource)); + int32_t x1 = x; + int32_t y1 = y; + int32_t x2 = x + width; + int32_t y2 = y + height; + + sl_transform_guest_to_host(host->ctx, nullptr, &x1, &y1); + sl_transform_guest_to_host(host->ctx, nullptr, &x2, &y2); + + wl_region_subtract(host->proxy, x1, y1, x2 - x1, y2 - y1); +} + +static const struct wl_region_interface sl_region_implementation = { + sl_region_destroy, sl_region_add, sl_region_subtract}; + +static void sl_destroy_host_region(struct wl_resource* resource) { + struct sl_host_region* host = + static_cast(wl_resource_get_user_data(resource)); + + wl_region_destroy(host->proxy); + wl_resource_set_user_data(resource, NULL); + delete host; +} + +static void sl_compositor_create_host_surface(struct wl_client* client, + struct wl_resource* resource, + uint32_t id) { + TRACE_EVENT("surface", "sl_compositor_create_host_surface"); + struct sl_host_compositor* host = + static_cast(wl_resource_get_user_data(resource)); + struct sl_window *window, *unpaired_window = NULL; + struct sl_host_surface* host_surface = new sl_host_surface(); + + host_surface->ctx = host->compositor->ctx; + host_surface->contents_width = 0; + host_surface->contents_height = 0; + host_surface->contents_x_offset = 0; + host_surface->contents_y_offset = 0; + host_surface->contents_shm_format = 0; + host_surface->contents_scale = 1; + wl_list_init(&host_surface->contents_viewport); + host_surface->contents_shm_mmap = NULL; + host_surface->has_role = 0; + host_surface->has_output = 0; + host_surface->has_own_scale = 0; + host_surface->xdg_scale_x = 0; + host_surface->xdg_scale_y = 0; + host_surface->scale_round_on_x = false; + host_surface->scale_round_on_y = false; + host_surface->last_event_serial = 0; + host_surface->current_buffer = NULL; + host_surface->proxy_buffer = NULL; + host_surface->contents_shaped = false; + host_surface->output = NULL; + pixman_region32_init(&host_surface->contents_shape); + wl_list_init(&host_surface->released_buffers); + wl_list_init(&host_surface->busy_buffers); + host_surface->resource = wl_resource_create( + client, &wl_surface_interface, wl_resource_get_version(resource), id); + wl_resource_set_implementation(host_surface->resource, + &sl_surface_implementation, host_surface, + sl_destroy_host_surface); + host_surface->proxy = wl_compositor_create_surface(host->proxy); + wl_surface_add_listener(host_surface->proxy, &sl_surface_listener, + host_surface); + if (host_surface->ctx->linux_explicit_synchronization && + host_surface->ctx->use_explicit_fence) { + host_surface->surface_sync = + zwp_linux_explicit_synchronization_v1_get_synchronization( + host_surface->ctx->linux_explicit_synchronization->internal, + host_surface->proxy); + } else { + host_surface->surface_sync = NULL; + } + host_surface->viewport = NULL; + if (host_surface->ctx->viewporter) { + host_surface->viewport = wp_viewporter_get_viewport( + host_surface->ctx->viewporter->internal, host_surface->proxy); + } + + wl_list_for_each(window, &host->compositor->ctx->unpaired_windows, link) { + if (window->host_surface_id == id) { + unpaired_window = window; + break; + } + } + + if (unpaired_window) + sl_window_update(window); +} + +static void sl_compositor_create_host_region(struct wl_client* client, + struct wl_resource* resource, + uint32_t id) { + struct sl_host_compositor* host = + static_cast(wl_resource_get_user_data(resource)); + struct sl_host_region* host_region = new sl_host_region(); + + host_region->ctx = host->compositor->ctx; + host_region->resource = wl_resource_create( + client, &wl_region_interface, wl_resource_get_version(resource), id); + wl_resource_set_implementation(host_region->resource, + &sl_region_implementation, host_region, + sl_destroy_host_region); + host_region->proxy = wl_compositor_create_region(host->proxy); + wl_region_set_user_data(host_region->proxy, host_region); +} + +static const struct wl_compositor_interface sl_compositor_implementation = { + sl_compositor_create_host_surface, sl_compositor_create_host_region}; + +static void sl_destroy_host_compositor(struct wl_resource* resource) { + struct sl_host_compositor* host = + static_cast(wl_resource_get_user_data(resource)); + + wl_compositor_destroy(host->proxy); + wl_resource_set_user_data(resource, NULL); + delete host; +} + +// Called when a Wayland client binds to our wl_compositor global. +// `version` is the version requested by the client. +static void sl_bind_host_compositor(struct wl_client* client, + void* data, + uint32_t version, + uint32_t id) { + struct sl_context* ctx = (struct sl_context*)data; + struct sl_host_compositor* host = new sl_host_compositor(); + host->compositor = ctx->compositor; + + // Create the client-facing wl_compositor resource using the requested + // version (or Sommelier's max supported version, whichever is lower). + // + // Sommelier requires a host compositor with wl_compositor version 3+, + // but exposes wl_compositor v4 to its clients (if --support-damage-buffer + // is passed). This is achieved by implementing wl_surface::damage_buffer (the + // only v4 feature) in terms of the existing wl_surface::damage request. + uint32_t maxSupportedVersion = ctx->support_damage_buffer + ? WL_SURFACE_DAMAGE_BUFFER_SINCE_VERSION + : kMinHostWlCompositorVersion; + host->resource = wl_resource_create(client, &wl_compositor_interface, + MIN(version, maxSupportedVersion), id); + wl_resource_set_implementation(host->resource, &sl_compositor_implementation, + host, sl_destroy_host_compositor); + + // Forward the bind request to the host, using the host's wl_compositor + // version (which may be different from Sommelier's version). + host->proxy = static_cast(wl_registry_bind( + wl_display_get_registry(ctx->display), ctx->compositor->id, + &wl_compositor_interface, kMinHostWlCompositorVersion)); + wl_compositor_set_user_data(host->proxy, host); +} + +struct sl_global* sl_compositor_global_create(struct sl_context* ctx) { + assert(ctx->compositor); + // Compute the compositor version to advertise to clients, depending on the + // --support-damage-buffer flag (see explanation above). + int compositorVersion = ctx->support_damage_buffer + ? WL_SURFACE_DAMAGE_BUFFER_SINCE_VERSION + : kMinHostWlCompositorVersion; + return sl_global_create(ctx, &wl_compositor_interface, compositorVersion, ctx, + sl_bind_host_compositor); +} + +void sl_compositor_init_context(struct sl_context* ctx, + struct wl_registry* registry, + uint32_t id, + uint32_t version) { + struct sl_compositor* compositor = + static_cast(malloc(sizeof(struct sl_compositor))); + assert(compositor); + compositor->ctx = ctx; + compositor->id = id; + assert(version >= kMinHostWlCompositorVersion); + compositor->internal = static_cast(wl_registry_bind( + registry, id, &wl_compositor_interface, kMinHostWlCompositorVersion)); + assert(!ctx->compositor); + ctx->compositor = compositor; + compositor->host_global = sl_compositor_global_create(ctx); +} diff --git a/sommelier/sommelier-drm.c b/sommelier/compositor/sommelier-drm.cc similarity index 75% rename from sommelier/sommelier-drm.c rename to sommelier/compositor/sommelier-drm.cc index a81540c..f6e9c13 100644 --- a/sommelier/sommelier-drm.c +++ b/sommelier/compositor/sommelier-drm.cc @@ -1,20 +1,22 @@ -// Copyright 2018 The Chromium OS Authors. All rights reserved. +// Copyright 2018 The ChromiumOS Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "sommelier.h" +#include "../sommelier.h" // NOLINT(build/include_directory) +#include "../sommelier-tracing.h" // NOLINT(build/include_directory) #include #include +#include #include #include #include #include -#include "virtgpu_drm.h" +#include "../virtualization/linux-headers/virtgpu_drm.h" // NOLINT(build/include_directory) -#include "drm-server-protocol.h" -#include "linux-dmabuf-unstable-v1-client-protocol.h" +#include "drm-server-protocol.h" // NOLINT(build/include_directory) +#include "linux-dmabuf-unstable-v1-client-protocol.h" // NOLINT(build/include_directory) struct sl_host_drm { struct sl_context* ctx; @@ -27,6 +29,7 @@ struct sl_host_drm { static void sl_drm_authenticate(struct wl_client* client, struct wl_resource* resource, uint32_t id) { + TRACE_EVENT("drm", "sl_drm_authenticate"); wl_drm_send_authenticated(resource); } @@ -68,6 +71,7 @@ static void sl_drm_sync(struct sl_context* ctx, // device given to sommelier. memset(&prime_handle, 0, sizeof(prime_handle)); prime_handle.fd = sync_point->fd; + TRACE_EVENT("drm", "sl_drm_sync", "prime_fd", prime_handle.fd); ret = drmIoctl(drm_fd, DRM_IOCTL_PRIME_FD_TO_HANDLE, &prime_handle); if (!ret) { struct drm_virtgpu_3d_wait wait_arg; @@ -100,7 +104,9 @@ static void sl_drm_create_prime_buffer(struct wl_client* client, int32_t stride1, int32_t offset2, int32_t stride2) { - struct sl_host_drm* host = wl_resource_get_user_data(resource); + TRACE_EVENT("drm", "sl_drm_create_prime_buffer", "id", id); + struct sl_host_drm* host = + static_cast(wl_resource_get_user_data(resource)); struct zwp_linux_buffer_params_v1* buffer_params; assert(name >= 0); @@ -125,17 +131,19 @@ static void sl_drm_create_prime_buffer(struct wl_client* client, prime_handle.fd = name; ret = drmIoctl(drm_fd, DRM_IOCTL_PRIME_FD_TO_HANDLE, &prime_handle); if (!ret) { - struct drm_virtgpu_resource_info info_arg; + struct drm_virtgpu_resource_info_cros info_arg; struct drm_gem_close gem_close; // Then attempts to get resource information. This will fail silently if // the drm device passed to sommelier is not a virtio-gpu device. memset(&info_arg, 0, sizeof(info_arg)); info_arg.bo_handle = prime_handle.handle; - ret = drmIoctl(drm_fd, DRM_IOCTL_VIRTGPU_RESOURCE_INFO, &info_arg); + info_arg.type = VIRTGPU_RESOURCE_INFO_TYPE_EXTENDED; + ret = drmIoctl(drm_fd, DRM_IOCTL_VIRTGPU_RESOURCE_INFO_CROS, &info_arg); // Correct stride0 if we are able to get proper resource info. if (!ret) { - stride0 = info_arg.stride; + if (info_arg.stride) + stride0 = info_arg.stride; is_gpu_buffer = 1; } @@ -148,17 +156,40 @@ static void sl_drm_create_prime_buffer(struct wl_client* client, buffer_params = zwp_linux_dmabuf_v1_create_params(host->ctx->linux_dmabuf->internal); - zwp_linux_buffer_params_v1_add(buffer_params, name, 0, offset0, stride0, 0, - 0); + zwp_linux_buffer_params_v1_add(buffer_params, name, 0, offset0, stride0, + DRM_FORMAT_MOD_INVALID >> 32, + DRM_FORMAT_MOD_INVALID & 0xffffffff); struct sl_host_buffer* host_buffer = - sl_create_host_buffer(client, id, + sl_create_host_buffer(host->ctx, client, id, zwp_linux_buffer_params_v1_create_immed( buffer_params, width, height, format, 0), - width, height); + width, height, /*is_drm=*/true); if (is_gpu_buffer) { host_buffer->sync_point = sl_sync_point_create(name); host_buffer->sync_point->sync = sl_drm_sync; + host_buffer->shm_format = sl_shm_format_for_drm_format(format); + + // Create our DRM PRIME mmap container + // This is simply a container that records necessary information + // to map the DRM buffer through the GBM API's. + // The GBM API's may need to perform a rather heavy copy of the + // buffer into memory accessible by the CPU to perform the mapping + // operation. + // For this reason, the GBM mapping API's will not be used until we + // are absolutely certain that the buffers contents need to be + // accessed. This will be done through a call to sl_mmap_begin_access. + // + // We are also checking for a single plane format as this container + // is currently only defined for single plane format buffers. + + if (sl_shm_num_planes_for_shm_format(host_buffer->shm_format) == 1) { + host_buffer->shm_mmap = sl_drm_prime_mmap_create( + host->ctx->gbm, name, + sl_shm_bpp_for_shm_format(host_buffer->shm_format), + sl_shm_num_planes_for_shm_format(host_buffer->shm_format), stride0, + width, height, format); + } } else { close(name); } @@ -171,18 +202,20 @@ static const struct wl_drm_interface sl_drm_implementation = { sl_drm_create_prime_buffer}; static void sl_destroy_host_drm(struct wl_resource* resource) { - struct sl_host_drm* host = wl_resource_get_user_data(resource); + struct sl_host_drm* host = + static_cast(wl_resource_get_user_data(resource)); zwp_linux_dmabuf_v1_destroy(host->linux_dmabuf_proxy); wl_callback_destroy(host->callback); wl_resource_set_user_data(resource, NULL); - free(host); + delete host; } static void sl_drm_format(void* data, struct zwp_linux_dmabuf_v1* linux_dmabuf, uint32_t format) { - struct sl_host_drm* host = zwp_linux_dmabuf_v1_get_user_data(linux_dmabuf); + struct sl_host_drm* host = static_cast( + zwp_linux_dmabuf_v1_get_user_data(linux_dmabuf)); switch (format) { case WL_DRM_FORMAT_RGB565: @@ -191,6 +224,7 @@ static void sl_drm_format(void* data, case WL_DRM_FORMAT_XRGB8888: case WL_DRM_FORMAT_XBGR8888: wl_drm_send_format(host->resource, format); + break; default: break; } @@ -208,7 +242,8 @@ static const struct zwp_linux_dmabuf_v1_listener sl_linux_dmabuf_listener = { static void sl_drm_callback_done(void* data, struct wl_callback* callback, uint32_t serial) { - struct sl_host_drm* host = wl_callback_get_user_data(callback); + struct sl_host_drm* host = + static_cast(wl_callback_get_user_data(callback)); if (host->ctx->drm_device) wl_drm_send_device(host->resource, host->ctx->drm_device); @@ -224,10 +259,7 @@ static void sl_bind_host_drm(struct wl_client* client, uint32_t version, uint32_t id) { struct sl_context* ctx = (struct sl_context*)data; - struct sl_host_drm* host; - - host = malloc(sizeof(*host)); - assert(host); + struct sl_host_drm* host = new sl_host_drm(); host->ctx = ctx; host->version = MIN(version, 2); host->resource = @@ -235,15 +267,13 @@ static void sl_bind_host_drm(struct wl_client* client, wl_resource_set_implementation(host->resource, &sl_drm_implementation, host, sl_destroy_host_drm); - host->linux_dmabuf_proxy = wl_registry_bind( + host->linux_dmabuf_proxy = static_cast(wl_registry_bind( wl_display_get_registry(ctx->display), ctx->linux_dmabuf->id, - &zwp_linux_dmabuf_v1_interface, ctx->linux_dmabuf->version); - zwp_linux_dmabuf_v1_set_user_data(host->linux_dmabuf_proxy, host); + &zwp_linux_dmabuf_v1_interface, ctx->linux_dmabuf->version)); zwp_linux_dmabuf_v1_add_listener(host->linux_dmabuf_proxy, &sl_linux_dmabuf_listener, host); host->callback = wl_display_sync(ctx->display); - wl_callback_set_user_data(host->callback, host); wl_callback_add_listener(host->callback, &sl_drm_callback_listener, host); } diff --git a/sommelier/compositor/sommelier-mmap.cc b/sommelier/compositor/sommelier-mmap.cc new file mode 100644 index 0000000..a5fe43d --- /dev/null +++ b/sommelier/compositor/sommelier-mmap.cc @@ -0,0 +1,173 @@ +// Copyright 2021 The ChromiumOS Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sommelier-mmap.h" // NOLINT(build/include_directory) + +#include +#include + +#include "../sommelier.h" // NOLINT(build/include_directory) +#include "../sommelier-tracing.h" // NOLINT(build/include_directory) + +struct sl_mmap* sl_mmap_create(int fd, + size_t size, + size_t bpp, + size_t num_planes, + size_t offset0, + size_t stride0, + size_t offset1, + size_t stride1, + size_t y_ss0, + size_t y_ss1) { + TRACE_EVENT("shm", "sl_mmap_create"); + struct sl_mmap* map = new sl_mmap(); + map->refcount = 1; + map->fd = fd; + map->size = size; + map->map_type = SL_MMAP_SHM; + map->gbm_map_data = NULL; + map->gbmbo = NULL; + map->num_planes = num_planes; + map->bpp = bpp; + map->offset[0] = offset0; + map->stride[0] = stride0; + map->offset[1] = offset1; + map->stride[1] = stride1; + map->y_ss[0] = y_ss0; + map->y_ss[1] = y_ss1; + map->begin_write = NULL; + map->end_write = NULL; + map->buffer_resource = NULL; + map->addr = + mmap(NULL, size + offset0, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + errno_assert(map->addr != MAP_FAILED); + + return map; +} + +struct sl_mmap* sl_drm_prime_mmap_create(gbm_device* device, + int fd, + size_t bpp, + size_t num_planes, + size_t stride, + int32_t width, + int32_t height, + uint32_t drm_format) { + TRACE_EVENT("drm", "sl_drm_mmap_create"); + struct sl_mmap* map = new sl_mmap(); + assert(num_planes == 1); + + map->refcount = 1; + map->fd = fd; + map->num_planes = num_planes; + map->bpp = bpp; + map->offset[0] = 0; + map->stride[0] = stride; + map->offset[1] = 0; + map->stride[1] = 0; + map->y_ss[0] = 1; + map->y_ss[1] = 1; + map->begin_write = NULL; + map->end_write = NULL; + map->buffer_resource = NULL; + map->map_type = SL_MMAP_DRM_PRIME; + map->gbm_map_data = NULL; + map->addr = NULL; + map->gbmbo = NULL; + + // Prefill in the gbm_import_data structure + map->gbm_import_data.fd = fd; + map->gbm_import_data.width = width; + map->gbm_import_data.height = height; + map->gbm_import_data.stride = stride; + map->gbm_import_data.format = drm_format; + + // Save the device as we will need it later + map->gbm_device_object = device; + return map; +} + +struct sl_mmap* sl_mmap_ref(struct sl_mmap* map) { + TRACE_EVENT("shm", "sl_mmap_ref"); + map->refcount++; + return map; +} + +bool sl_mmap_begin_access(struct sl_mmap* map) { + uint32_t ret_stride; + + // This function is to be used on the DRM PRIME mmap path. + // It is used to ensure we can actually access the resource + // when its contents are needed. + // If we have any other mmap type, we should simply return true + // under the assumption that they do not need to perform this extra + // check + if (map->map_type != SL_MMAP_DRM_PRIME) + return true; + + // Attempt to import (and map) the GBM BO + // If we cannot do so, return false so the upper layers + // can respond appropriately. + map->gbmbo = gbm_bo_import(map->gbm_device_object, GBM_BO_IMPORT_FD, + reinterpret_cast(&map->gbm_import_data), + GBM_BO_USE_LINEAR); + if (!map->gbmbo) { + return false; + } + + map->gbm_map_data = NULL; + map->addr = gbm_bo_map(map->gbmbo, 0, 0, map->gbm_import_data.width, + map->gbm_import_data.height, GBM_BO_TRANSFER_READ, + &ret_stride, &map->gbm_map_data); + if (!map->addr) { + gbm_bo_destroy(map->gbmbo); + return false; + } + + map->stride[0] = ret_stride; + map->size = ret_stride * map->gbm_import_data.height; + + return true; +} + +void sl_mmap_end_access(struct sl_mmap* map) { + if (map->map_type != SL_MMAP_DRM_PRIME) + return; + + if (map->addr && map->gbm_map_data) { + gbm_bo_unmap(map->gbmbo, map->gbm_map_data); + map->addr = NULL; + map->gbm_map_data = NULL; + } + + if (map->gbmbo) { + gbm_bo_destroy(map->gbmbo); + map->gbmbo = NULL; + } +} + +void sl_mmap_unref(struct sl_mmap* map) { + TRACE_EVENT("shm", "sl_mmap_unref"); + if (map->refcount-- == 1) { + switch (map->map_type) { + case SL_MMAP_SHM: + munmap(map->addr, map->size + map->offset[0]); + if (map->fd != -1) + close(map->fd); + delete map; + break; + + case SL_MMAP_DRM_PRIME: + // Invoke end_access just in case + sl_mmap_end_access(map); + if (map->fd != -1) + close(map->fd); + delete map; + break; + + default: + break; + } + } +} diff --git a/sommelier/compositor/sommelier-mmap.h b/sommelier/compositor/sommelier-mmap.h new file mode 100644 index 0000000..39bdd30 --- /dev/null +++ b/sommelier/compositor/sommelier-mmap.h @@ -0,0 +1,65 @@ +// Copyright 2021 The ChromiumOS Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef VM_TOOLS_SOMMELIER_COMPOSITOR_SOMMELIER_MMAP_H_ +#define VM_TOOLS_SOMMELIER_COMPOSITOR_SOMMELIER_MMAP_H_ + +#include +#include + +typedef void (*sl_begin_end_access_func_t)(int fd, struct sl_context* ctx); + +enum slMmapType { + SL_MMAP_NONE, // None + SL_MMAP_SHM, // SHM mmap type + SL_MMAP_DRM_PRIME // DRM PRIME mmap type +}; + +struct sl_mmap { + int refcount; + int fd; + void* addr; + struct gbm_bo* gbmbo; + void* gbm_map_data; + gbm_device* gbm_device_object; + size_t size; + size_t bpp; + size_t num_planes; + size_t offset[2]; + size_t stride[2]; + size_t y_ss[2]; + sl_begin_end_access_func_t begin_write; + sl_begin_end_access_func_t end_write; + slMmapType map_type; + struct gbm_import_fd_data gbm_import_data; + struct wl_resource* buffer_resource; +}; + +struct sl_mmap* sl_drm_prime_mmap_create(gbm_device* device, + int fd, + size_t bpp, + size_t num_planes, + size_t stride, + int32_t width, + int32_t height, + uint32_t drm_format); + +struct sl_mmap* sl_mmap_create(int fd, + size_t size, + size_t bpp, + size_t num_planes, + size_t offset0, + size_t stride0, + size_t offset1, + size_t stride1, + size_t y_ss0, + size_t y_ss1); + +bool sl_mmap_begin_access(struct sl_mmap* map); +void sl_mmap_end_access(struct sl_mmap* map); + +struct sl_mmap* sl_mmap_ref(struct sl_mmap* map); +void sl_mmap_unref(struct sl_mmap* map); + +#endif // VM_TOOLS_SOMMELIER_COMPOSITOR_SOMMELIER_MMAP_H_ diff --git a/sommelier/sommelier-shm.c b/sommelier/compositor/sommelier-shm.cc similarity index 69% rename from sommelier/sommelier-shm.c rename to sommelier/compositor/sommelier-shm.cc index 1352aa8..678ca76 100644 --- a/sommelier/sommelier-shm.c +++ b/sommelier/compositor/sommelier-shm.cc @@ -1,16 +1,17 @@ -// Copyright 2018 The Chromium OS Authors. All rights reserved. +// Copyright 2018 The ChromiumOS Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "sommelier.h" +#include "../sommelier.h" // NOLINT(build/include_directory) +#include "../sommelier-tracing.h" // NOLINT(build/include_directory) #include #include #include #include -#include "drm-server-protocol.h" -#include "linux-dmabuf-unstable-v1-client-protocol.h" +#include "drm-server-protocol.h" // NOLINT(build/include_directory) +#include "linux-dmabuf-unstable-v1-client-protocol.h" // NOLINT(build/include_directory) struct sl_host_shm_pool { struct sl_shm* shm; @@ -129,28 +130,38 @@ static void sl_host_shm_pool_create_host_buffer(struct wl_client* client, int32_t height, int32_t stride, uint32_t format) { - struct sl_host_shm_pool* host = wl_resource_get_user_data(resource); + struct sl_host_shm_pool* host = + static_cast(wl_resource_get_user_data(resource)); - if (host->shm->ctx->shm_driver == SHM_DRIVER_NOOP) { + if (host->shm->ctx->channel == NULL) { + // Running in noop mode, without virtualization. assert(host->proxy); - sl_create_host_buffer(client, id, + sl_create_host_buffer(host->shm->ctx, client, id, wl_shm_pool_create_buffer(host->proxy, offset, width, height, stride, format), - width, height); - } else { - struct sl_host_buffer* host_buffer = - sl_create_host_buffer(client, id, NULL, width, height); - - host_buffer->shm_format = format; - host_buffer->shm_mmap = sl_mmap_create( - dup(host->fd), sl_size_for_shm_format(format, height, stride), - sl_shm_bpp_for_shm_format(format), - sl_shm_num_planes_for_shm_format(format), offset, stride, - offset + sl_offset_for_shm_format_plane(format, height, stride, 1), - stride, sl_y_subsampling_for_shm_format_plane(format, 0), - sl_y_subsampling_for_shm_format_plane(format, 1)); - host_buffer->shm_mmap->buffer_resource = host_buffer->resource; + width, height, /*is_drm=*/true); + return; } + + struct sl_host_buffer* host_buffer = sl_create_host_buffer( + host->shm->ctx, client, id, NULL, width, height, /*is_drm=*/false); + + host_buffer->shm_format = format; + host_buffer->shm_mmap = sl_mmap_create( + host->fd, sl_size_for_shm_format(format, height, stride), + sl_shm_bpp_for_shm_format(format), + sl_shm_num_planes_for_shm_format(format), offset, stride, + offset + sl_offset_for_shm_format_plane(format, height, stride, 1), + stride, sl_y_subsampling_for_shm_format_plane(format, 0), + sl_y_subsampling_for_shm_format_plane(format, 1)); + // In the case of mmaps created from the client buffer, we want to be able + // to close the FD when the client releases the shm pool (i.e. when it's + // done transferring) as opposed to when the pool is freed (i.e. when we're + // done drawing). + // We do this by removing the handle to the FD after it has been mmapped, + // which prevents a double-close. + host_buffer->shm_mmap->fd = -1; + host_buffer->shm_mmap->buffer_resource = host_buffer->resource; } static void sl_host_shm_pool_destroy(struct wl_client* client, @@ -161,7 +172,8 @@ static void sl_host_shm_pool_destroy(struct wl_client* client, static void sl_host_shm_pool_resize(struct wl_client* client, struct wl_resource* resource, int32_t size) { - struct sl_host_shm_pool* host = wl_resource_get_user_data(resource); + struct sl_host_shm_pool* host = + static_cast(wl_resource_get_user_data(resource)); if (host->proxy) wl_shm_pool_resize(host->proxy, size); @@ -172,14 +184,15 @@ static const struct wl_shm_pool_interface sl_shm_pool_implementation = { sl_host_shm_pool_resize}; static void sl_destroy_host_shm_pool(struct wl_resource* resource) { - struct sl_host_shm_pool* host = wl_resource_get_user_data(resource); + struct sl_host_shm_pool* host = + static_cast(wl_resource_get_user_data(resource)); if (host->fd >= 0) close(host->fd); if (host->proxy) wl_shm_pool_destroy(host->proxy); wl_resource_set_user_data(resource, NULL); - free(host); + delete host; } static void sl_shm_create_host_pool(struct wl_client* client, @@ -187,11 +200,9 @@ static void sl_shm_create_host_pool(struct wl_client* client, uint32_t id, int fd, int32_t size) { - struct sl_host_shm* host = wl_resource_get_user_data(resource); - struct sl_host_shm_pool* host_shm_pool; - - host_shm_pool = malloc(sizeof(*host_shm_pool)); - assert(host_shm_pool); + struct sl_host_shm* host = + static_cast(wl_resource_get_user_data(resource)); + struct sl_host_shm_pool* host_shm_pool = new sl_host_shm_pool(); host_shm_pool->shm = host->shm; host_shm_pool->fd = -1; @@ -202,17 +213,13 @@ static void sl_shm_create_host_pool(struct wl_client* client, &sl_shm_pool_implementation, host_shm_pool, sl_destroy_host_shm_pool); - switch (host->shm->ctx->shm_driver) { - case SHM_DRIVER_NOOP: - host_shm_pool->proxy = wl_shm_create_pool(host->shm_proxy, fd, size); - wl_shm_pool_set_user_data(host_shm_pool->proxy, host_shm_pool); - close(fd); - break; - case SHM_DRIVER_DMABUF: - case SHM_DRIVER_VIRTWL: - case SHM_DRIVER_VIRTWL_DMABUF: - host_shm_pool->fd = fd; - break; + if (host->shm->ctx->channel == NULL) { + // Running in noop mode, without virtualization. + host_shm_pool->proxy = wl_shm_create_pool(host->shm_proxy, fd, size); + wl_shm_pool_set_user_data(host_shm_pool->proxy, host_shm_pool); + close(fd); + } else { + host_shm_pool->fd = fd; } } @@ -220,7 +227,9 @@ static const struct wl_shm_interface sl_shm_implementation = { sl_shm_create_host_pool}; static void sl_shm_format(void* data, struct wl_shm* shm, uint32_t format) { - struct sl_host_shm* host = wl_shm_get_user_data(shm); + TRACE_EVENT("shm", "sl_shm_format"); + struct sl_host_shm* host = + static_cast(wl_shm_get_user_data(shm)); switch (format) { case WL_SHM_FORMAT_RGB565: @@ -229,6 +238,7 @@ static void sl_shm_format(void* data, struct wl_shm* shm, uint32_t format) { case WL_SHM_FORMAT_XRGB8888: case WL_SHM_FORMAT_XBGR8888: wl_shm_send_format(host->resource, format); + break; default: break; } @@ -239,7 +249,8 @@ static const struct wl_shm_listener sl_shm_listener = {sl_shm_format}; static void sl_drm_format(void* data, struct zwp_linux_dmabuf_v1* linux_dmabuf, uint32_t format) { - struct sl_host_shm* host = zwp_linux_dmabuf_v1_get_user_data(linux_dmabuf); + struct sl_host_shm* host = static_cast( + zwp_linux_dmabuf_v1_get_user_data(linux_dmabuf)); // Forward SHM versions of supported formats. switch (format) { @@ -274,14 +285,15 @@ static const struct zwp_linux_dmabuf_v1_listener sl_linux_dmabuf_listener = { sl_drm_format, sl_drm_modifier}; static void sl_destroy_host_shm(struct wl_resource* resource) { - struct sl_host_shm* host = wl_resource_get_user_data(resource); + struct sl_host_shm* host = + static_cast(wl_resource_get_user_data(resource)); if (host->shm_proxy) wl_shm_destroy(host->shm_proxy); if (host->linux_dmabuf_proxy) zwp_linux_dmabuf_v1_destroy(host->linux_dmabuf_proxy); wl_resource_set_user_data(resource, NULL); - free(host); + delete host; } static void sl_bind_host_shm(struct wl_client* client, @@ -289,10 +301,7 @@ static void sl_bind_host_shm(struct wl_client* client, uint32_t version, uint32_t id) { struct sl_context* ctx = (struct sl_context*)data; - struct sl_host_shm* host; - - host = malloc(sizeof(*host)); - assert(host); + struct sl_host_shm* host = new sl_host_shm(); host->shm = ctx->shm; host->shm_proxy = NULL; host->linux_dmabuf_proxy = NULL; @@ -300,29 +309,22 @@ static void sl_bind_host_shm(struct wl_client* client, wl_resource_set_implementation(host->resource, &sl_shm_implementation, host, sl_destroy_host_shm); - switch (ctx->shm_driver) { - case SHM_DRIVER_NOOP: - case SHM_DRIVER_VIRTWL: - host->shm_proxy = wl_registry_bind( - wl_display_get_registry(ctx->display), ctx->shm->id, - &wl_shm_interface, wl_resource_get_version(host->resource)); - wl_shm_set_user_data(host->shm_proxy, host); - wl_shm_add_listener(host->shm_proxy, &sl_shm_listener, host); - break; - case SHM_DRIVER_VIRTWL_DMABUF: - case SHM_DRIVER_DMABUF: - assert(ctx->linux_dmabuf); - host->linux_dmabuf_proxy = wl_registry_bind( - wl_display_get_registry(ctx->display), ctx->linux_dmabuf->id, - &zwp_linux_dmabuf_v1_interface, - wl_resource_get_version(host->resource)); - zwp_linux_dmabuf_v1_set_user_data(host->linux_dmabuf_proxy, host); - zwp_linux_dmabuf_v1_add_listener(host->linux_dmabuf_proxy, - &sl_linux_dmabuf_listener, host); - break; + if (ctx->channel != NULL && ctx->channel->supports_dmabuf()) { + assert(ctx->linux_dmabuf); + host->linux_dmabuf_proxy = static_cast( + wl_registry_bind(wl_display_get_registry(ctx->display), + ctx->linux_dmabuf->id, &zwp_linux_dmabuf_v1_interface, + wl_resource_get_version(host->resource))); + zwp_linux_dmabuf_v1_add_listener(host->linux_dmabuf_proxy, + &sl_linux_dmabuf_listener, host); + } else { + host->shm_proxy = static_cast(wl_registry_bind( + wl_display_get_registry(ctx->display), ctx->shm->id, &wl_shm_interface, + wl_resource_get_version(host->resource))); + wl_shm_add_listener(host->shm_proxy, &sl_shm_listener, host); } } struct sl_global* sl_shm_global_create(struct sl_context* ctx) { return sl_global_create(ctx, &wl_shm_interface, 1, ctx, sl_bind_host_shm); -} \ No newline at end of file +} diff --git a/sommelier/config.h b/sommelier/config.h deleted file mode 100644 index 7f9f75b..0000000 --- a/sommelier/config.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef _CONFIG_H_ -#define _CONFIG_H_ - -/* -#ifndef XWAYLAND_PATH -#define XWAYLAND_PATH "/usr/bin/Xwayland" -#endif -*/ -#define XWAYLAND_GL_DRIVER_PATH "/lib/x86_64-linux-gnu/dri" -#define XWAYLAND_SHM_DRIVER "virtwl" -#define SHM_DRIVER "virtwl" -#define VIRTWL_DEVICE "/dev/wl0" -#define PEER_CMD_PREFIX "" -#define FRAME_COLOR "#f2f2f2" -#define DARK_FRAME_COLOR "#323639" -#endif diff --git a/sommelier/demos/wayland_demo.cc b/sommelier/demos/wayland_demo.cc deleted file mode 100644 index a5c9d1b..0000000 --- a/sommelier/demos/wayland_demo.cc +++ /dev/null @@ -1,319 +0,0 @@ -// Copyright 2018 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include -#include -#include -#include -#include -#include - -#include "base/command_line.h" -#include "base/logging.h" -#include "base/memory/shared_memory.h" -#include "base/strings/string_number_conversions.h" -#include "brillo/syslog_logging.h" - -constexpr char kBgColorFlag[] = "bgcolor"; -constexpr char kWidthFlag[] = "width"; -constexpr char kHeightFlag[] = "height"; -constexpr char kTitleFlag[] = "title"; - -struct demo_data { - uint32_t bgcolor; - uint32_t width; - uint32_t height; - std::string title; - int scale; - struct wl_compositor* compositor; - struct wl_shell* shell; - struct wl_shm* shm; - struct wl_surface* surface; - struct wl_shell_surface* shell_surface; - struct wl_buffer* buffer; - struct wl_callback* callback; - struct wl_callback_listener* callback_listener; - struct wl_output* output; - struct wl_output_listener* output_listener; - struct wl_keyboard_listener* keyboard_listener; - void* shm_ptr; - bool done; -}; - -void keyboard_keymap(void* data, - struct wl_keyboard* keyboard, - uint32_t format, - int32_t fd, - uint32_t size) {} - -void keyboard_enter(void* data, - struct wl_keyboard* keyboard, - uint32_t serial, - struct wl_surface* surface, - struct wl_array* keys) {} - -void keyboard_leave(void* data, - struct wl_keyboard* keyboard, - uint32_t serial, - struct wl_surface* surface) {} - -void keyboard_key(void* data, - struct wl_keyboard* keyboard, - uint32_t serial, - uint32_t time, - uint32_t key, - uint32_t state) { - struct demo_data* data_ptr = reinterpret_cast(data); - // Key pressed. - if (state == 1) { - LOG(INFO) << "wayland_demo application detected keypress"; - data_ptr->done = true; - } -} - -void keyboard_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) {} - -void keyboard_repeat_info(void* data, - struct wl_keyboard* keyboard, - int32_t rate, - int32_t delay) {} - -void demo_registry_listener(void* data, - struct wl_registry* registry, - uint32_t id, - const char* interface, - uint32_t version) { - struct demo_data* data_ptr = reinterpret_cast(data); - if (!strcmp("wl_compositor", interface)) { - data_ptr->compositor = reinterpret_cast( - wl_registry_bind(registry, id, &wl_compositor_interface, version)); - } else if (!strcmp("wl_shell", interface)) { - data_ptr->shell = reinterpret_cast( - wl_registry_bind(registry, id, &wl_shell_interface, version)); - } else if (!strcmp("wl_shm", interface)) { - data_ptr->shm = reinterpret_cast( - wl_registry_bind(registry, id, &wl_shm_interface, version)); - } else if (!strcmp("wl_output", interface)) { - data_ptr->output = reinterpret_cast( - wl_registry_bind(registry, id, &wl_output_interface, version)); - wl_output_add_listener(data_ptr->output, data_ptr->output_listener, - data_ptr); - } else if (!strcmp("wl_seat", interface)) { - struct wl_seat* seat = reinterpret_cast( - wl_registry_bind(registry, id, &wl_seat_interface, version)); - wl_keyboard_add_listener(wl_seat_get_keyboard(seat), - data_ptr->keyboard_listener, data_ptr); - } -} - -void demo_registry_remover(void* data, - struct wl_registry* registry, - uint32_t id) {} - -void shell_surface_ping(void* data, - struct wl_shell_surface* shell_surface, - uint32_t serial) { - wl_shell_surface_pong(shell_surface, serial); -} - -void shell_surface_configure(void* data, - struct wl_shell_surface* shell_surface, - uint32_t edges, - int32_t width, - int32_t height) {} - -void shell_surface_popup_done(void* data, - struct wl_shell_surface* shell_surface) {} - -void demo_draw(void* data, struct wl_callback* callback, uint32_t time) { - struct demo_data* data_ptr = reinterpret_cast(data); - wl_callback_destroy(data_ptr->callback); - wl_surface_damage(data_ptr->surface, 0, 0, data_ptr->width, data_ptr->height); - uint32_t* surface_data = reinterpret_cast(data_ptr->shm_ptr); - for (int i = 0; i < data_ptr->width * data_ptr->height; ++i) { - surface_data[i] = data_ptr->bgcolor; - } - data_ptr->callback = wl_surface_frame(data_ptr->surface); - wl_surface_attach(data_ptr->surface, data_ptr->buffer, 0, 0); - wl_callback_add_listener(data_ptr->callback, data_ptr->callback_listener, - data_ptr); - wl_surface_commit(data_ptr->surface); -} - -void output_geometry(void* data, - struct wl_output* output, - int32_t x, - int32_t y, - int32_t physical_width, - int32_t physical_height, - int32_t subpixel, - const char* make, - const char* model, - int32_t transform) {} - -void output_mode(void* data, - struct wl_output* output, - uint32_t flags, - int32_t width, - int32_t height, - int32_t refresh) { - struct demo_data* data_ptr = reinterpret_cast(data); - if (data_ptr->width == 0) { - data_ptr->width = width; - if (data_ptr->scale != 0) { - data_ptr->width /= data_ptr->scale; - } - } - if (data_ptr->height == 0) { - data_ptr->height = height; - if (data_ptr->scale != 0) { - data_ptr->height /= data_ptr->scale; - } - } -} - -void output_done(void* data, struct wl_output* output) {} - -void output_scale(void* data, struct wl_output* output, int32_t factor) { - struct demo_data* data_ptr = reinterpret_cast(data); - data_ptr->scale = factor; - if (data_ptr->width != 0) { - data_ptr->width /= factor; - } - if (data_ptr->height != 0) { - data_ptr->height /= factor; - } -} - -int main(int argc, char* argv[]) { - brillo::InitLog(brillo::kLogToSyslog); - LOG(INFO) << "Starting wayland_demo application"; - - base::CommandLine::Init(argc, argv); - base::CommandLine* cl = base::CommandLine::ForCurrentProcess(); - struct demo_data data; - memset(&data, 0, sizeof(data)); - data.done = false; - - data.bgcolor = 0x3388DD; - if (cl->HasSwitch(kBgColorFlag)) { - data.bgcolor = - strtoul(cl->GetSwitchValueASCII(kBgColorFlag).c_str(), nullptr, 0); - } - if (cl->HasSwitch(kWidthFlag)) { - if (!base::StringToUint(cl->GetSwitchValueASCII(kWidthFlag), &data.width)) { - LOG(ERROR) << "Invalid width parameter passed"; - return -1; - } - } - if (cl->HasSwitch(kHeightFlag)) { - if (!base::StringToUint(cl->GetSwitchValueASCII(kHeightFlag), - &data.height)) { - LOG(ERROR) << "Invalid height parameter passed"; - return -1; - } - } - data.title = "wayland_demo"; - if (cl->HasSwitch(kTitleFlag)) { - data.title = cl->GetSwitchValueASCII(kTitleFlag); - } - - struct wl_display* display = wl_display_connect(nullptr); - if (!display) { - LOG(ERROR) << "Failed connecting to display"; - return -1; - } - - struct wl_output_listener output_listener = {output_geometry, output_mode, - output_done, output_scale}; - data.output_listener = &output_listener; - struct wl_registry_listener registry_listener = { - demo_registry_listener, demo_registry_remover, - }; - struct wl_keyboard_listener keyboard_listener = { - keyboard_keymap, keyboard_enter, keyboard_leave, - keyboard_key, keyboard_modifiers, keyboard_repeat_info}; - data.keyboard_listener = &keyboard_listener; - - struct wl_registry* registry = wl_display_get_registry(display); - wl_registry_add_listener(registry, ®istry_listener, &data); - - wl_display_dispatch(display); - wl_display_roundtrip(display); - - if (!data.compositor) { - LOG(ERROR) << "Failed to find compositor"; - return -1; - } - if (!data.output) { - LOG(ERROR) << "Failed to get output"; - return -1; - } - - // Do another roundtrip to ensure we get the wl_output callbacks. - wl_display_roundtrip(display); - - data.surface = wl_compositor_create_surface(data.compositor); - if (!data.surface) { - LOG(ERROR) << "Failed creating surface"; - return -1; - } - if (!data.shell) { - LOG(ERROR) << "Failed getting shell"; - return -1; - } - - data.shell_surface = wl_shell_get_shell_surface(data.shell, data.surface); - if (!data.shell_surface) { - LOG(ERROR) << "Failed getting shell surface"; - return -1; - } - const struct wl_shell_surface_listener shell_surface_listener = { - shell_surface_ping, shell_surface_configure, shell_surface_popup_done}; - wl_shell_surface_add_listener(data.shell_surface, &shell_surface_listener, - nullptr); - - wl_shell_surface_set_toplevel(data.shell_surface); - wl_shell_surface_set_class(data.shell_surface, data.title.c_str()); - wl_shell_surface_set_title(data.shell_surface, data.title.c_str()); - data.callback = wl_surface_frame(data.surface); - struct wl_callback_listener callback_listener = {demo_draw}; - data.callback_listener = &callback_listener; - wl_callback_add_listener(data.callback, data.callback_listener, &data); - - if (!data.shm) { - LOG(ERROR) << "Failed getting shared memory"; - return -1; - } - - size_t stride = data.width * 4 /* 32bpp */; - size_t shm_size = stride * data.height; - base::SharedMemory shared_mem; - shared_mem.CreateAndMapAnonymous(shm_size); - data.shm_ptr = shared_mem.memory(); - - struct wl_shm_pool* pool = - wl_shm_create_pool(data.shm, shared_mem.handle().fd, shm_size); - data.buffer = wl_shm_pool_create_buffer(pool, 0, data.width, data.height, - stride, WL_SHM_FORMAT_XRGB8888); - wl_shm_pool_destroy(pool); - - wl_surface_attach(data.surface, data.buffer, 0, 0); - wl_surface_commit(data.surface); - - demo_draw(&data, nullptr, 0); - LOG(INFO) << "wayland_demo application displaying, waiting for keypress"; - do { - } while (wl_display_dispatch(display) != -1 && !data.done); - - wl_display_disconnect(display); - LOG(INFO) << "wayland_demo application exiting"; - return 0; -} diff --git a/sommelier/demos/x11_demo.cc b/sommelier/demos/x11_demo.cc deleted file mode 100644 index 3345a73..0000000 --- a/sommelier/demos/x11_demo.cc +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright 2018 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include -#include -#include -#include -#include - -#include "base/command_line.h" -#include "base/logging.h" -#include "base/strings/string_number_conversions.h" -#include "brillo/syslog_logging.h" - -constexpr char kBgColorFlag[] = "bgcolor"; -constexpr char kWidthFlag[] = "width"; -constexpr char kHeightFlag[] = "height"; -constexpr char kTitleFlag[] = "title"; - -// Creates an X window the same size as the display and fills its background -// with a solid color that can be specified as the only parameter (in hex or -// base 10). Closes on any keypress. -int main(int argc, char* argv[]) { - brillo::InitLog(brillo::kLogToSyslog); - LOG(INFO) << "Starting x11_demo application"; - - base::CommandLine::Init(argc, argv); - base::CommandLine* cl = base::CommandLine::ForCurrentProcess(); - uint32_t bgcolor = 0x99EE44; - if (cl->HasSwitch(kBgColorFlag)) { - bgcolor = - strtoul(cl->GetSwitchValueASCII(kBgColorFlag).c_str(), nullptr, 0); - } - std::string title = "x11_demo"; - if (cl->HasSwitch(kTitleFlag)) { - title = cl->GetSwitchValueASCII(kTitleFlag); - } - - Display* dpy = XOpenDisplay(nullptr); - if (!dpy) { - LOG(ERROR) << "Failed opening display"; - return -1; - } - - int screen = DefaultScreen(dpy); - Window win; - int x, y; - unsigned int width, height, border, depth; - if (XGetGeometry(dpy, RootWindow(dpy, screen), &win, &x, &y, &width, &height, - &border, &depth) == 0) { - LOG(ERROR) << "Failed getting screen geometry"; - return -1; - } - if (cl->HasSwitch(kWidthFlag)) { - if (!base::StringToUint(cl->GetSwitchValueASCII(kWidthFlag), &width)) { - LOG(ERROR) << "Invalid width parameter passed"; - return -1; - } - } - if (cl->HasSwitch(kHeightFlag)) { - if (!base::StringToUint(cl->GetSwitchValueASCII(kHeightFlag), &height)) { - LOG(ERROR) << "Invalid height parameter passed"; - return -1; - } - } - win = XCreateSimpleWindow(dpy, RootWindow(dpy, screen), x, y, width, height, - 0, 0 /* black */, bgcolor); - - XClassHint* wmclass_hint = XAllocClassHint(); - wmclass_hint->res_name = wmclass_hint->res_class = strdup(title.c_str()); - XSetClassHint(dpy, win, wmclass_hint); - XSelectInput(dpy, win, KeyPressMask); - XMapWindow(dpy, win); - XStoreName(dpy, win, title.c_str()); - - LOG(INFO) << "x11_demo application displaying, waiting for keypress"; - XEvent evt; - for (;;) { - XNextEvent(dpy, &evt); - if (evt.type == KeyPress) { - LOG(INFO) << "x11_demo application detected keypress"; - break; - } - } - - XCloseDisplay(dpy); - LOG(INFO) << "x11_demo application exiting"; - return 0; -} diff --git a/sommelier/linux/virtio_wl.h b/sommelier/linux/virtio_wl.h deleted file mode 100644 index 1d3ec6b..0000000 --- a/sommelier/linux/virtio_wl.h +++ /dev/null @@ -1,135 +0,0 @@ -#ifndef _LINUX_VIRTIO_WL_H -#define _LINUX_VIRTIO_WL_H -/* - * This header is BSD licensed so anyone can use the definitions to implement - * compatible drivers/servers. - */ -#include -#include -#include - -#define VIRTWL_IN_BUFFER_SIZE 4096 -#define VIRTWL_OUT_BUFFER_SIZE 4096 -#define VIRTWL_VQ_IN 0 -#define VIRTWL_VQ_OUT 1 -#define VIRTWL_QUEUE_COUNT 2 -#define VIRTWL_MAX_ALLOC 0x800 -#define VIRTWL_PFN_SHIFT 12 - -/* Enables the transition to new flag semantics */ -#define VIRTIO_WL_F_TRANS_FLAGS 1 - -struct virtio_wl_config { -}; - -/* - * The structure of each of these is virtio_wl_ctrl_hdr or one of its subclasses - * where noted. - */ -enum virtio_wl_ctrl_type { - VIRTIO_WL_CMD_VFD_NEW = 0x100, /* virtio_wl_ctrl_vfd_new */ - VIRTIO_WL_CMD_VFD_CLOSE, /* virtio_wl_ctrl_vfd */ - VIRTIO_WL_CMD_VFD_SEND, /* virtio_wl_ctrl_vfd_send + data */ - VIRTIO_WL_CMD_VFD_RECV, /* virtio_wl_ctrl_vfd_recv + data */ - VIRTIO_WL_CMD_VFD_NEW_CTX, /* virtio_wl_ctrl_vfd_new */ - VIRTIO_WL_CMD_VFD_NEW_PIPE, /* virtio_wl_ctrl_vfd_new */ - VIRTIO_WL_CMD_VFD_HUP, /* virtio_wl_ctrl_vfd */ - VIRTIO_WL_CMD_VFD_NEW_DMABUF, /* virtio_wl_ctrl_vfd_new */ - VIRTIO_WL_CMD_VFD_DMABUF_SYNC, /* virtio_wl_ctrl_vfd_dmabuf_sync */ - VIRTIO_WL_CMD_VFD_SEND_FOREIGN_ID, /* virtio_wl_ctrl_vfd_send + data */ - - VIRTIO_WL_RESP_OK = 0x1000, - VIRTIO_WL_RESP_VFD_NEW = 0x1001, /* virtio_wl_ctrl_vfd_new */ - VIRTIO_WL_RESP_VFD_NEW_DMABUF = 0x1002, /* virtio_wl_ctrl_vfd_new */ - - VIRTIO_WL_RESP_ERR = 0x1100, - VIRTIO_WL_RESP_OUT_OF_MEMORY, - VIRTIO_WL_RESP_INVALID_ID, - VIRTIO_WL_RESP_INVALID_TYPE, - VIRTIO_WL_RESP_INVALID_FLAGS, - VIRTIO_WL_RESP_INVALID_CMD, -}; - -struct virtio_wl_ctrl_hdr { - __le32 type; /* one of virtio_wl_ctrl_type */ - __le32 flags; /* always 0 */ -}; - -enum virtio_wl_vfd_flags { - VIRTIO_WL_VFD_WRITE = 0x1, /* intended to be written by guest */ - VIRTIO_WL_VFD_READ = 0x2, /* intended to be read by guest */ -}; - -struct virtio_wl_ctrl_vfd { - struct virtio_wl_ctrl_hdr hdr; - __le32 vfd_id; -}; - -/* - * If this command is sent to the guest, it indicates that the VFD has been - * created and the fields indicate the properties of the VFD being offered. - * - * If this command is sent to the host, it represents a request to create a VFD - * of the given properties. The pfn field is ignored by the host. - */ -struct virtio_wl_ctrl_vfd_new { - struct virtio_wl_ctrl_hdr hdr; - __le32 vfd_id; /* MSB indicates device allocated vfd */ - __le32 flags; /* virtio_wl_vfd_flags */ - __le64 pfn; /* first guest physical page frame number if VFD_MAP */ - __le32 size; /* size in bytes if VIRTIO_WL_CMD_VFD_NEW* */ - /* buffer description if VIRTIO_WL_CMD_VFD_NEW_DMABUF */ - struct { - __le32 width; /* width in pixels */ - __le32 height; /* height in pixels */ - __le32 format; /* fourcc format */ - __le32 stride0; /* return stride0 */ - __le32 stride1; /* return stride1 */ - __le32 stride2; /* return stride2 */ - __le32 offset0; /* return offset0 */ - __le32 offset1; /* return offset1 */ - __le32 offset2; /* return offset2 */ - } dmabuf; -}; - - -enum virtio_wl_ctrl_vfd_send_kind { - /* The id after this one indicates an ordinary vfd_id. */ - VIRTIO_WL_CTRL_VFD_SEND_KIND_LOCAL, - /* The id after this one is a virtio-gpu resource id. */ - VIRTIO_WL_CTRL_VFD_SEND_KIND_VIRTGPU, -}; - -struct virtio_wl_ctrl_vfd_send_vfd { - __le32 kind; /* virtio_wl_ctrl_vfd_send_kind */ - __le32 id; -}; - -struct virtio_wl_ctrl_vfd_send { - struct virtio_wl_ctrl_hdr hdr; - __le32 vfd_id; - __le32 vfd_count; /* struct is followed by this many IDs */ - - /* - * If hdr.type == VIRTIO_WL_CMD_VFD_SEND_FOREIGN_ID, there is a - * vfd_count array of virtio_wl_ctrl_vfd_send_vfd. Otherwise, there is a - * vfd_count array of vfd_ids. - */ - - /* the remainder is raw data */ -}; - -struct virtio_wl_ctrl_vfd_recv { - struct virtio_wl_ctrl_hdr hdr; - __le32 vfd_id; - __le32 vfd_count; /* struct is followed by this many IDs */ - /* the remainder is raw data */ -}; - -struct virtio_wl_ctrl_vfd_dmabuf_sync { - struct virtio_wl_ctrl_hdr hdr; - __le32 vfd_id; - __le32 flags; -}; - -#endif /* _LINUX_VIRTIO_WL_H */ diff --git a/sommelier/meson.build b/sommelier/meson.build new file mode 100644 index 0000000..1315733 --- /dev/null +++ b/sommelier/meson.build @@ -0,0 +1,192 @@ +# Copyright 2020 The ChromiumOS Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +project('sommelier', 'c', 'cpp', + default_options : ['cpp_std=c++17']) + +#===============# +# Configuration # +#===============# + +includes = [] +cpp_args = [] + +#===============# +# Wayland Stuff # +#===============# + +wl_scanner = find_program('wayland-scanner') + +wl_generators = [ + generator( + wl_scanner, + output: '@BASENAME@-code.c', + arguments: ['private-code', '@INPUT@', '@OUTPUT@'] + ), + generator( + wl_scanner, + output: '@BASENAME@-client-protocol.h', + arguments: ['client-header', '@INPUT@', '@OUTPUT@'] + ), + generator( + wl_scanner, + output: '@BASENAME@-server-protocol.h', + arguments: ['server-header', '@INPUT@', '@OUTPUT@'] + ), +] + +wl_protocols = [ + 'protocol/aura-shell.xml', + 'protocol/drm.xml', + 'protocol/gaming-input-unstable-v2.xml', + 'protocol/gtk-shell.xml', + 'protocol/keyboard-extension-unstable-v1.xml', + 'protocol/linux-dmabuf-unstable-v1.xml', + 'protocol/linux-explicit-synchronization-unstable-v1.xml', + 'protocol/pointer-constraints-unstable-v1.xml', + 'protocol/relative-pointer-unstable-v1.xml', + 'protocol/text-input-unstable-v1.xml', + 'protocol/text-input-extension-unstable-v1.xml', + 'protocol/text-input-x11-unstable-v1.xml', + 'protocol/viewporter.xml', + 'protocol/xdg-output-unstable-v1.xml', + 'protocol/xdg-shell.xml', +] + +wl_outs = [] + +foreach p : wl_protocols + foreach g : wl_generators + wl_outs += g.process(p) + endforeach +endforeach + +#==========# +# Perfetto # +#==========# + +tracing_sources = [] +tracing_dependencies = [] + +if get_option('tracing') + tracing_dependencies = [ + dependency('threads'), + dependency('perfetto'), + ] + cpp_args += '-DPERFETTO_TRACING' +endif + +#=================# +# Gamepad support # +#=================# + +gamepad_sources = [] +gamepad_dependencies = [] + +if get_option('gamepad') + gamepad_sources = [ + 'sommelier-gaming.cc', + ] + gamepad_dependencies = [ + dependency('libevdev'), + ] + cpp_args += '-DGAMEPAD_SUPPORT' +endif + +#===========# +# Sommelier # +#===========# + +if get_option('commit_loop_fix') + cpp_args += '-DCOMMIT_LOOP_FIX' +endif + +if get_option('black_screen_fix') + cpp_args += '-DBLACK_SCREEN_FIX' +endif + +sommelier_defines = [ + '-D_GNU_SOURCE', + '-DWL_HIDE_DEPRECATED', + '-DXWAYLAND_PATH="' + get_option('xwayland_path') + '"', + '-DXWAYLAND_GL_DRIVER_PATH="' + get_option('xwayland_gl_driver_path') + '"', + '-DFRAME_COLOR="' + get_option('frame_color') + '"', + '-DDARK_FRAME_COLOR="' + get_option('dark_frame_color') + '"', +] + +libsommelier = static_library('sommelier', + sources: [ + 'compositor/sommelier-compositor.cc', + 'compositor/sommelier-drm.cc', + 'compositor/sommelier-mmap.cc', + 'compositor/sommelier-shm.cc', + 'sommelier-ctx.cc', + 'sommelier-data-device-manager.cc', + 'sommelier-display.cc', + 'sommelier-gtk-shell.cc', + 'sommelier-global.cc', + 'sommelier-output.cc', + 'sommelier-pointer-constraints.cc', + 'sommelier-relative-pointer-manager.cc', + 'sommelier-seat.cc', + 'sommelier-shell.cc', + 'sommelier-subcompositor.cc', + 'sommelier-text-input.cc', + 'sommelier-timing.cc', + 'sommelier-tracing.cc', + 'sommelier-transform.cc', + 'sommelier-util.cc', + 'sommelier-viewporter.cc', + 'sommelier-xdg-shell.cc', + 'sommelier-xshape.cc', + 'sommelier.cc', + 'sommelier-window.cc', + 'virtualization/virtwl_channel.cc', + 'virtualization/virtgpu_channel.cc', + ] + wl_outs + tracing_sources + gamepad_sources, + dependencies: [ + meson.get_compiler('cpp').find_library('m'), + dependency('gbm'), + dependency('libdrm'), + dependency('pixman-1'), + dependency('wayland-client'), + dependency('wayland-server'), + dependency('xcb'), + dependency('xcb-composite'), + dependency('xcb-shape'), + dependency('xcb-xfixes'), + dependency('xkbcommon'), + ] + tracing_dependencies + gamepad_dependencies, + cpp_args: cpp_args + sommelier_defines, + include_directories: includes, +) + +executable('sommelier', + install: true, + sources: [ + 'sommelier-main.cc', + ] + wl_outs, + link_with: libsommelier, + cpp_args: cpp_args + sommelier_defines, + include_directories: includes, +) + +if get_option('with_tests') + sommelier_test = executable('sommelier_test', + install: true, + sources: [ + 'sommelier_test.cc', + ] + wl_outs, + link_with: libsommelier, + dependencies: [ + dependency('gtest'), + dependency('gmock'), + dependency('pixman-1') + ], + cpp_args: cpp_args + sommelier_defines, + include_directories: includes, + ) + + test('sommelier_test', sommelier_test) +endif diff --git a/sommelier/meson_options.txt b/sommelier/meson_options.txt new file mode 100644 index 0000000..144ad09 --- /dev/null +++ b/sommelier/meson_options.txt @@ -0,0 +1,59 @@ +# Copyright 2020 The ChromiumOS Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +option('xwayland_path', + type: 'string', + value: '/opt/google/cros-containers/bin/Xwayland', + description: 'path to Xwayland' +) + +option('xwayland_gl_driver_path', + type: 'string', + value: '/opt/google/cros-containers/lib', + description: 'the GL driver path to use for Xwayland' +) + +option('frame_color', + type: 'string', + value: '#f2f2f2', + description: 'frame color to use for Xwayland clients' +) + +option('dark_frame_color', + type: 'string', + value: '#323639', + description: 'dark frame color to use for Xwayland clients' +) + +option('tracing', + type: 'boolean', + value: false, + description: 'enable tracing via perfetto' +) + +option('gamepad', + type: 'boolean', + value: false, + description: 'enable gamepad support' +) + +# TODO(b/181077580): remove this in favour of a proper fix to the busy +# loop issue. +option('commit_loop_fix', + type: 'boolean', + value: false, + description: 'enable a fix to the commit-cycle, which is known to break some apps' +) + +option('black_screen_fix', + type: 'boolean', + value: false, + description: 'enable a fix to fix some apps blackscreening when losing focus' +) + +option('with_tests', + type: 'boolean', + value: true, + description: 'build the sommelier_test target' +) diff --git a/sommelier/protocol/aura-shell.xml b/sommelier/protocol/aura-shell.xml index 319d1b2..f778d83 100644 --- a/sommelier/protocol/aura-shell.xml +++ b/sommelier/protocol/aura-shell.xml @@ -2,7 +2,7 @@ - Copyright 2017 The Chromium Authors. + Copyright 2017 The Chromium Authors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), @@ -24,7 +24,7 @@ DEALINGS IN THE SOFTWARE. - + The global interface exposing aura shell capabilities is used to instantiate an interface extension for a wl_surface object. @@ -66,9 +66,101 @@ + + + + + + Specifies the server's window layout mode. + + + + + + + + Sends the layout_mode used by the server. + + + + + + + + + Sends a monorail ID of a bug fixed on the exo server that clients can + use to gate functionality. + + + + + + + Notifies when there is a change in global desks state. This is emitted on + desk name changes, desk addition/removal or desks are reordered. + "desk_names" argument contains the set of null terminated strings as names of desks. + + + + + + Notifies when there is a change of the active desk. + + + + + + + + Notifies client that the activated surface changed. + + + + + + + + + [Deprecated] Informs the server that when submitting surfaces, this + client will not use wl_surface_set_buffer_scale to report the scales, + nor will it apply scale via vp_viewporter. Instead the server should + apply an appropriate scale transform to have the submitted buffers + composited correctly. + + + + + + Retrieve the aura toplevel interface for a given xdg toplevel interface + + + + + + + + Retrieve the aura popup interface for the given xdg popup interface + + + + + + + + Using this request a client can tell the server that it is not going to + use the zaura_shell object anymore. This does not affect any other objects. + + This is named "release" because "destroy" is a special named function used for + freeing wl_proxy objects. Naming it "destroy" will cause marshalling errors + when running on lower versioned hosts. All "release" requests here should be + renamed to "destroy" if we move to aura-shell v2. + + - + An additional interface to a wl_surface object, which allows the client to access aura shell specific functionality for surface. @@ -85,7 +177,9 @@ - Suggests a surface should use a specific frame. + [Deprecated] Suggests a surface should use a specific frame. Deprecated + since M105. See the set_decoration method on zaura_toplevel and + zaura_popup. @@ -129,9 +223,320 @@ + + + + + + Deprecated. Please use set_client_surface_str_id instead. + Set the identifier of the surface assigned by the client. + + + + + + + + + Enum describing why an occlusion change happened. An occlusion change as a + result of a user action could include things like the user moving a window, + changing occlusion, or opening/closing a window, changing the occlusion. + + + + + + + Sets occlusion tracking on this surface. The client will be updated with a + new occlusion fraction when the amount of occlusion of this surface changes. + + + + + + Unsets occlusion tracking for this surface. + + + + + + Notifies when there is a change in the amount this surface is occluded. + The occlusion update is sent as a fixed point number from 0 to 1, representing + the proportion of occlusion. + + + + + + + + + + Make this the active window. This usually implies something like + restacking this surface to the foreground. The compositor is free to + ignore this request if it deems the client to be misbehaving. Typically + this request will only be honoured in response to some user driven + event, such as executing an application or opening a file in a window + that already exists. + + + + + + Draw attention to this surface in a way that does not change the user's + focus. This usually means animating window decorations or taskbar icons. + The compositor can still ignore this request if it deems fit, but unlike + draw_focus, these requests are expected to come from background tasks, + and are more likely to be honoured. + + + + + + + + Possible windowing system behaviors if this surface were to go + fullscreen. + + + + + + + + Suggests how the windowing system should behave if this surface were + to go fullscreen. Does not make the surface fullscreen. + + Typically the default mode is "immersive". + + + + + + + + + Set the identifier of the surface assigned by the client. + + + + + + + + + Suggests a surface to have client-side decoration, but + server-side decides when and where to start the resize. The server may also + apply visual effects to indicate that the resize operation is ongoing. + + + + + + + + Surface snap directions. + + + + + + + + + Notify (or inform) the server the client's intent to snap the window. + To inform it's no longer willing to snap, send 'none'. + + + + + + + Request that surface is snapped to the left. + + + + + + Request that surface is snapped to the right. + + + + + + Request that surface resets snapping. + + + + + + + + Notifies the client to lock window in normal or restore state. When + window is locked, the window frame should look like it is in restored + state, but actually isn't. Locking happends while dragging a maximized + window. + + + + + + Notifies the client to unlock window if it is previously locked. + Unlocking happends while dragging a maximized window. + + + + + + + Set window session id to the surface. + + + + + + + + Sets that the surface can go back as per its navigation list. + This allows the server to react to by minimizing the window upon a + system wide back gesture. + + + + + + Unsets that the surface can go back as per its navigation list. + See above. + + + + + + + Requests that the surface is set to Picture-in-Picture (PIP). + + + + + + Requests that the surface is unset from Picture-in-Picture (PIP). + + + + + + Sets the aspect ratio of the surface. + + + + + + + + + Describes the occlusion state of a surface. + + + + + + + + + + Notifies the client that the occlusion state of a window has changed. Clients + will only receive these messages if they previously request occlusion tracking + via set_occlusion_tracking for a particular surface. + + + + + + + + If |index| equals -1, requests that the server toggles whether client + is visible on all workspaces. + If |index| is not -1, requests that the server moves the client to the desk at + |index|. + + + + + + + Notifies when there is a change in the desk state of a window. + This is emitted when a window is moved to another desk or + when its assigned-to-all-desks state changes. + + + + + + + + If |initial_workspace| equals '-1', a window is restored and visible on all workspaces, + Otherwise, set the initial workspace to restore the window to the corresponding workspace. + This is not double buffered and must be set before attaching a buffer. + + + + + + + + Requests that a window is pinned which means that the system does not allow + the user to leave the window until an exit criteria is met. + + This is a request to get the window pinned so that the user cannot get to any + other window / application. There are two modes: + A. trusted is 0 - which is slightly less restrictive and allows the user to + get out of the window by a predefined way of authentication. + B. trusted is not 0 in which case a trusted application was locking down the + system and needs to unlock. This is used for e.g. School tests. + + + + + + + Requests that the user can leave a previously pinned window. + + This is a request to unpin a previously pinned window. It does not matter if + the window was locked with the trusted state or not. + + + + + + Informs the client to start throttling on the surface. + + + + + Informs the client to end throttling on the surface. + + + + + + + Destroy the zaura_surface object. A client should destroy this object when the + role is unmapped from a wl_surface. + + See zaura_shell.release for destructor naming. + + - + An additional interface to a wl_output object, which allows the client to access aura shell specific functionality for output. @@ -233,6 +638,270 @@ - + + + + + This event describes the insets for the output in logical screen + coordinates, from which the work area can be calculated. + + This event is sent before wl_output.done, after which the client would + apply the change. + + + + + + + + + + + + This event describes the logical transform for the output. Whereas + wl_output.geometry's transform corresponds to the display's panel + rotation, the logical transform corresponds to the display's logical + rotation. + + This event is sent before wl_output.done, after which the client would + apply the change. + + + + + + + + Destroy this zaura_shell object. + + Destroying a bound zaura_shell object while there are zaura_surfaces + still alive created by this zaura_shell object instance is illegal + and will result in a protocol error. + + See zaura_shell.release for destructor naming. + + + + + + + An interface to the toplevel shell, which allows the + client to access shell specific functionality. + + + + + Defines orientation request when a surface is in fullscreen. + + + + + + + + + + + + + + Request a specific orientation behavior when this surface is in fullscreen. + + + + + + Informs the server that when submitting this surface, this client will not + use wl_surface_set_buffer_scale to report the scales, nor will it apply + scale via vp_viewporter. Instead the server should apply an appropriate + scale transform for the submitted buffers to be composited correctly. + + + + + + Requesting this will enable screen coordinates in surfaces + associated with aura_toplevel including sub surfaces and popup + windows who added this toplevel as a parent. This should be + set before first commit. + + + + + + Request a new location and bounds of the surface in DP screen + coordinates. The size will be applied to visible bounds used + in set_geometry. The output is a hint for the compositor to + determine which output the window should move to. These + parameters are just a request and the compositor may ignore, + adjust the size and position based on the rule imposed by the + window manager, or may preserve it for future operations. For + example, the compositor will not allow a position outside of + the output, or the compositor may just store it if the + toplevel surface is in maximiezd state, and may use it upon + unmaximized. + + + + + + + + + + + A configuration change that also includes the window origin in screen coordinates. + + + + + + + + + + + The states that are contained here are supplemental to the states + defined in the XDG shell and specific aura windows. + + + + + + User can access system UIs such as the shelf and window frame + by pointing to, or swiping over, the screen edge. + + + + + The window has been minimized. + + + + + + + A notification sent when the window origin has changed. Unlike a configure, + this does not imply the client needs to resize. The values are in screen + coordinates. + + + + + + + + Request session id and restore id of a newly created browser window. + Set the information used by compositor to restore the toplevel + surface state, such as window position, window state, upon creation. + This is not double buffered and must be set before sending first commit. + + + + + + + + + Requests that the toplevel surface should become a system modal. The + compositor will prevent other windows from receiving events. If there + are multiple system modal surfaces, the compositor will decide which + one to receive events. + + + + + + Requests that the system modal state of the toplevel surface will be + unset. The compositor will then allow other windows to recieve events. + + + + + + Request session id and restore id of the window. Set the information used by compositor to restore the toplevel surface state, such as window position, window state, upon creation. This is not double buffered and must be set before sending first commit. This is different from set_restore_info, used for clients that create multiple windows associated with restore_id_source. + + + + + + + + Clients are allowed to request a particular decoration for a + zaura_toplevel. The server is not required to honor this request. See + decoration_type for available options. Available since M105. + + + + + + + Decoration types are used to modify the surface (e.g. drop shadow). + + + + + + + + + + Destroy this zaura_toplevel object. A client should call destroy when the role + is unmapped from a wl_surface. + + See zaura_shell.release for destructor naming. + + + + + + + An interface to the popup shell, which allows the + client to access shell specific functionality. + + + + + Informs the server that when submitting this surface, this client will not + use wl_surface_set_buffer_scale to report the scales, nor will it apply + scale via vp_viewporter. Instead the server should apply an appropriate + scale transform for the submitted buffers to be composited correctly. + + + + + + Clients are allowed to request a particular decoration for a + zaura_toplevel. The server is not required to honor this request. See + decoration_type for available options. Available since M105. + + + + + + + Decoration types are used to modify the surface (e.g. drop shadow). + + + + + + + + + Set popup type to menu + + + + + + + This request destroys the zaura_popup. A client should call destroy when the + role is unmapped from a wl_surface. + + See zaura_shell.release for destructor naming. + + + diff --git a/sommelier/protocol/gaming-input-unstable-v2.xml b/sommelier/protocol/gaming-input-unstable-v2.xml new file mode 100644 index 0000000..ab139a7 --- /dev/null +++ b/sommelier/protocol/gaming-input-unstable-v2.xml @@ -0,0 +1,244 @@ + + + + + Copyright 2016 The Chromium Authors + + 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. + + + + + A global interface to provide gaming input devices for a given seat. + + Currently only gamepad devices are supported. + + Warning! The protocol described in this file is experimental and + backward incompatible changes may be made. Backward compatible changes + may be added together with the corresponding uinterface version bump. + Backward incompatible changes are done by bumping the version number in + the protocol and uinterface names and resetting the interface version. + Once the protocol is to be declared stable, the 'z' prefix and the + version number in the protocol and interface names are removed and the + interface version number is reset. + + + + + Get a gaming seat object for a given seat. Gaming seat provides access + to gaming devices + + + + + + + + Destroy gaming_input object. Objects created from this object are + unaffected and should be destroyed separately. + + + + + + + An object that provides access to all the gaming devices of a seat. + When a gamepad is connected, the compositor will send gamepad_added event. + + + + + Destroy gaming_seat object. Objects created from this object are + unaffected and should be destroyed separately. + + + + + + Notification that there is gamepad connected at this seat. + + + + + + + Device connection type e.g. Bluetooth + + + + + + + + Notification that there is gamepad connected at this seat. + + + + + + + + + + + + + The zcr_gamepad_v2 interface represents one or more gamepad input devices, + which are reported as a normalized 'Standard Gamepad' as it is specified + by the W3C Gamepad API at: https://w3c.github.io/gamepad/#remapping + + + + + Destroy gamepad. Instances created from this gamepad are unaffected + and should be destroyed separately. + + + + + + Removed event is send when the gamepad is disconnected. The client should + expect no more event and call destroy. + + This event cannot be used as destructor as requests (e.g. vibration) might + be added to this interface. + + + + + + Notification of axis change. + + The axis id specifies which axis has changed as defined by the W3C + 'Standard Gamepad'. + + The value is calibrated and normalized to the -1 to 1 range. + + + + + + + + + Describes the physical state of a button that produced the button + event. + + + + + + + + Notification of button change. + + The button id specifies which button has changed as defined by the W3C + 'Standard Gamepad'. + + A button can have a digital and an analog value. The analog value is + normalized to a 0 to 1 range. + If a button does not provide an analog value, it will be derived from + the digital state. + + + + + + + + + + Indicates the end of a set of events that logically belong together. + A client is expected to accumulate the data in all events within the + frame before proceeding. + + + + + + + Adds an axis to the gamepad. Only called when the gamepad was created by + gamepad_added_with_device_info. The values are compatible with + input_absinfo. + + + + + + + + + + + + Activates the gamepad i.e. the gamepad will be visible to applications + after this event is fired. All axis_added events should be sent before + this event. Only called when the gamepad was created by + gamepad_added_with_device_info. + + + + + + + Adds a vibrator to the gamepad. Only called if server has verified + that gamepad has a vibrator. The vibrator(s) for a gamepad are expected + to be added before the "activated" event is called. + + + + + + + + An interface that provides access to the vibrator of a gamepad. Requests can be + sent to make the gamepad vibrate and to stop an ongoing vibration. + + + + + Triggers the vibration event on the gamepad vibrator. The gamepad is only allowed to + vibrate while the window is in focus. The values in the timings array are 64-bit integers + and the values in the amplitudes array are unsigned 8-bit integers. + The timings array and the amplitudes array are of the same length. + For each timing/amplitude pair, the amplitude determines the strength of + the vibration and the timing determines the length of the vibration in milliseconds. + Amplitude values must be between 0 and 255. An amplitude of 0 implies no vibration + and any timing/amplitude pair with a timing value of 0 is ignored. + The repeat argument determines the index at which the vibration pattern to repeat begins. + A repeat value of -1 disables repetition. If repetition is enabled, the vibration + pattern will repeat indefinitely until stopped, or when focus is lost. + + + + + + + + + Cancels the currently ongoing vibration event on the gamepad vibrator. + + + + + + + + diff --git a/sommelier/protocol/keyboard-extension-unstable-v1.xml b/sommelier/protocol/keyboard-extension-unstable-v1.xml index a90604d..b4840d7 100644 --- a/sommelier/protocol/keyboard-extension-unstable-v1.xml +++ b/sommelier/protocol/keyboard-extension-unstable-v1.xml @@ -2,7 +2,7 @@ - Copyright 2017 The Chromium Authors. + Copyright 2017 The Chromium Authors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/sommelier/protocol/linux-explicit-synchronization-unstable-v1.xml b/sommelier/protocol/linux-explicit-synchronization-unstable-v1.xml new file mode 100644 index 0000000..bf93de4 --- /dev/null +++ b/sommelier/protocol/linux-explicit-synchronization-unstable-v1.xml @@ -0,0 +1,256 @@ + + + + + Copyright 2016 The Chromium Authors + Copyright 2017 Intel Corporation + Copyright 2018 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. + + + + + This global is a factory interface, allowing clients to request + explicit synchronization for buffers on a per-surface basis. + + See zwp_linux_surface_synchronization_v1 for more information. + + This interface is derived from Chromium's + zcr_linux_explicit_synchronization_v1. + + Warning! The protocol described in this file is experimental and + backward incompatible changes may be made. Backward compatible changes + may be added together with the corresponding interface version bump. + Backward incompatible changes are done by bumping the version number in + the protocol and interface names and resetting the interface version. + Once the protocol is to be declared stable, the 'z' prefix and the + version number in the protocol and interface names are removed and the + interface version number is reset. + + + + + Destroy this explicit synchronization factory object. Other objects, + including zwp_linux_surface_synchronization_v1 objects created by this + factory, shall not be affected by this request. + + + + + + + + + + Instantiate an interface extension for the given wl_surface to provide + explicit synchronization. + + If the given wl_surface already has an explicit synchronization object + associated, the synchronization_exists protocol error is raised. + + Graphics APIs, like EGL or Vulkan, that manage the buffer queue and + commits of a wl_surface themselves, are likely to be using this + extension internally. If a client is using such an API for a + wl_surface, it should not directly use this extension on that surface, + to avoid raising a synchronization_exists protocol error. + + + + + + + + + + This object implements per-surface explicit synchronization. + + Synchronization refers to co-ordination of pipelined operations performed + on buffers. Most GPU clients will schedule an asynchronous operation to + render to the buffer, then immediately send the buffer to the compositor + to be attached to a surface. + + In implicit synchronization, ensuring that the rendering operation is + complete before the compositor displays the buffer is an implementation + detail handled by either the kernel or userspace graphics driver. + + By contrast, in explicit synchronization, dma_fence objects mark when the + asynchronous operations are complete. When submitting a buffer, the + client provides an acquire fence which will be waited on before the + compositor accesses the buffer. The Wayland server, through a + zwp_linux_buffer_release_v1 object, will inform the client with an event + which may be accompanied by a release fence, when the compositor will no + longer access the buffer contents due to the specific commit that + requested the release event. + + Each surface can be associated with only one object of this interface at + any time. + + In version 1 of this interface, explicit synchronization is only + guaranteed to be supported for buffers created with any version of the + wp_linux_dmabuf buffer factory. Version 2 additionally guarantees + explicit synchronization support for opaque EGL buffers, which is a type + of platform specific buffers described in the EGL_WL_bind_wayland_display + extension. Compositors are free to support explicit synchronization for + additional buffer types. + + + + + Destroy this explicit synchronization object. + + Any fence set by this object with set_acquire_fence since the last + commit will be discarded by the server. Any fences set by this object + before the last commit are not affected. + + zwp_linux_buffer_release_v1 objects created by this object are not + affected by this request. + + + + + + + + + + + + + + + Set the acquire fence that must be signaled before the compositor + may sample from the buffer attached with wl_surface.attach. The fence + is a dma_fence kernel object. + + The acquire fence is double-buffered state, and will be applied on the + next wl_surface.commit request for the associated surface. Thus, it + applies only to the buffer that is attached to the surface at commit + time. + + If the provided fd is not a valid dma_fence fd, then an INVALID_FENCE + error is raised. + + If a fence has already been attached during the same commit cycle, a + DUPLICATE_FENCE error is raised. + + If the associated wl_surface was destroyed, a NO_SURFACE error is + raised. + + If at surface commit time the attached buffer does not support explicit + synchronization, an UNSUPPORTED_BUFFER error is raised. + + If at surface commit time there is no buffer attached, a NO_BUFFER + error is raised. + + + + + + + Create a listener for the release of the buffer attached by the + client with wl_surface.attach. See zwp_linux_buffer_release_v1 + documentation for more information. + + The release object is double-buffered state, and will be associated + with the buffer that is attached to the surface at wl_surface.commit + time. + + If a zwp_linux_buffer_release_v1 object has already been requested for + the surface in the same commit cycle, a DUPLICATE_RELEASE error is + raised. + + If the associated wl_surface was destroyed, a NO_SURFACE error + is raised. + + If at surface commit time there is no buffer attached, a NO_BUFFER + error is raised. + + + + + + + + This object is instantiated in response to a + zwp_linux_surface_synchronization_v1.get_release request. + + It provides an alternative to wl_buffer.release events, providing a + unique release from a single wl_surface.commit request. The release event + also supports explicit synchronization, providing a fence FD for the + client to synchronize against. + + Exactly one event, either a fenced_release or an immediate_release, will + be emitted for the wl_surface.commit request. The compositor can choose + release by release which event it uses. + + This event does not replace wl_buffer.release events; servers are still + required to send those events. + + Once a buffer release object has delivered a 'fenced_release' or an + 'immediate_release' event it is automatically destroyed. + + + + + Sent when the compositor has finalised its usage of the associated + buffer for the relevant commit, providing a dma_fence which will be + signaled when all operations by the compositor on that buffer for that + commit have finished. + + Once the fence has signaled, and assuming the associated buffer is not + pending release from other wl_surface.commit requests, no additional + explicit or implicit synchronization is required to safely reuse or + destroy the buffer. + + This event destroys the zwp_linux_buffer_release_v1 object. + + + + + + + Sent when the compositor has finalised its usage of the associated + buffer for the relevant commit, and either performed no operations + using it, or has a guarantee that all its operations on that buffer for + that commit have finished. + + Once this event is received, and assuming the associated buffer is not + pending release from other wl_surface.commit requests, no additional + explicit or implicit synchronization is required to safely reuse or + destroy the buffer. + + This event destroys the zwp_linux_buffer_release_v1 object. + + + + + \ No newline at end of file diff --git a/sommelier/protocol/text-input-extension-unstable-v1.xml b/sommelier/protocol/text-input-extension-unstable-v1.xml new file mode 100644 index 0000000..5578d7b --- /dev/null +++ b/sommelier/protocol/text-input-extension-unstable-v1.xml @@ -0,0 +1,263 @@ + + + + + Copyright 2021 The Chromium Authors + + 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. + + + + + Allows a text_input to sends more variation of operations to support + richer features, such as set_preedit_region. + + Warning! The protocol described in this file is experimental and + backward incompatible changes may be made. Backward compatible changes + may be added together with the corresponding uinterface version bump. + Backward incompatible changes are done by bumping the version number in + the protocol and interface names and resetting the interface version. + Once the protocol is to be declared stable, the 'z' prefix and the + version number in the protocol and interface names are removed and the + interface version number is reset. + + + + + + + + + Create extended_text_input object. + See zcr_extended_text_input interface for details. + If the given text_input object already has a extended_text_input object + associated, the extended_text_input_exists protocol error is raised. + + + + + + + + + + The zcr_extended_text_input_v1 interface extends the text_input interface + to support more rich operations on text_input. + + + + + + + + + IME requests to update text_input client to set the preedit + from the surrounding text. + + index is the starting point of the preedit, relative to the current + cursor position in utf-8 byte-offset. + length is the length of the preedit region in utf-8 byte length. + + Following the convention we have in text_input::preedit_string, + text_input::preedit_styling sent just before this will be applied. + + + + + + + + + + Wayland has its own input-type support, which is + zwp_text_input::content_purpose. However, it is not rich enough to + represent all Chrome's input types. This enum is introduced to keep + all entries so exo can understand it without any information loss. + See TextInputType's description for details about each entry. + + + + + + + + + + + + + + + + + + + + + + + + Similar to input_type defined above, this keeps Chrome's TextInputMode. + See TextInputMode's description for details for each entry. + + + + + + + + + + + + + + + Similar to input_type defined above, this keeps Chrome's TextInputFlags, + because content_hint is not enough power to represent what Chrome wants. + See TextInputFlags' description for details for each entry. + + + + + + + + + + + + + + + + + + + + + + + + In wayland, there's a concept that can be mapped to Chrome's + TextInputType, Mode and Flags. It can be set via + zwp_text_input::set_content_type. However, the variation is not rich + enough to represent Chrome's detailed behavior change. This API can be + used as a replacement of set_content_type. + + + + + + + + + + + + IME requests to clear all the grammar markers within the given range + defined by start and end. + + start and end are relative to the beginning of the input field in + utf-8 byte length. + + + + + + + + IME requests to add a new grammar fragment. + + A grammar fragment describes a range of text (start, end) that has + grammar error and also gives the correct replacement text. It is + expected that the renderer will render markers (e.g. squigles or dashed + underlines) under the text to notify users that there is a grammar + error. It is also expected that the renderer will maintain and update + the position of fragment when users edit other parts of the text, e.g. + if users type something before the grammar fragment, the marker should + move accordingly. + + start and end are relative to the beginning of the input field in + utf-8 byte length. suggestion is the correct replacement text, encoded + in utf-8 and suggested by ML model. + + + + + + + + + Informs the IME of the grammar fragment containing the current cursor. + If not existing, both start and end are set to 0. This is called + whenever the cursor position or surrounding text have changed. + + start and end are relative to the beginning of the input field in + utf-8 byte length. suggestion is the correct replacement text encoded + in utf-8 and suggested by ML model. + + + + + + + + + + + IME requests to update text_input client to set the autocorrect range. + There is only one autocorrect range, so this replaces any existing + autocorrect ranges. + + start and end are relative to the beginning of the input field in utf-8 + byte length. + + If start and end are the same, then the autocorrect range is cleared. + + + + + + + + Informs the IME the range and bounds of the current autocorrect change. + This is called whenever the range or bounds have changed. + + start and end are relative to the beginning of the input field in utf-8 + byte length. + + x, y, width, and height are the bounds of the autocorrect text, relative + to the window. + + This request only changes a pending state that will be effective on the + next 'set_surrounding_text' request. + + + + + + + + + + + diff --git a/sommelier/protocol/text-input-x11-unstable-v1.xml b/sommelier/protocol/text-input-x11-unstable-v1.xml new file mode 100644 index 0000000..e5acb0a --- /dev/null +++ b/sommelier/protocol/text-input-x11-unstable-v1.xml @@ -0,0 +1,26 @@ + + + + + Copyright 2022 The ChromiumOS Authors + Use of this source code is governed by a BSD-style license that can be + found in the LICENSE file. + + + + + Adds functionality for X11 apps running on Xwayland to connect to the + Wayland compositor and receive text_input support. + + + + + Calls text_input::activate with the X11 window id converted to the + matching wl_surface. + + + + + + + diff --git a/sommelier/protocol/xdg-output-unstable-v1.xml b/sommelier/protocol/xdg-output-unstable-v1.xml new file mode 100644 index 0000000..9a5b790 --- /dev/null +++ b/sommelier/protocol/xdg-output-unstable-v1.xml @@ -0,0 +1,220 @@ + + + + + Copyright © 2017 Red Hat Inc. + + 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. + + + + This protocol aims at describing outputs in a way which is more in line + with the concept of an output on desktop oriented systems. + + Some information are more specific to the concept of an output for + a desktop oriented system and may not make sense in other applications, + such as IVI systems for example. + + Typically, the global compositor space on a desktop system is made of + a contiguous or overlapping set of rectangular regions. + + Some of the information provided in this protocol might be identical + to their counterparts already available from wl_output, in which case + the information provided by this protocol should be preferred to their + equivalent in wl_output. The goal is to move the desktop specific + concepts (such as output location within the global compositor space, + the connector name and types, etc.) out of the core wl_output protocol. + + Warning! The protocol described in this file is experimental and + backward incompatible changes may be made. Backward compatible + changes may be added together with the corresponding interface + version bump. + Backward incompatible changes are done by bumping the version + number in the protocol and interface names and resetting the + interface version. Once the protocol is to be declared stable, + the 'z' prefix and the version number in the protocol and + interface names are removed and the interface version number is + reset. + + + + + A global factory interface for xdg_output objects. + + + + + Using this request a client can tell the server that it is not + going to use the xdg_output_manager object anymore. + + Any objects already created through this instance are not affected. + + + + + + This creates a new xdg_output object for the given wl_output. + + + + + + + + + An xdg_output describes part of the compositor geometry. + + This typically corresponds to a monitor that displays part of the + compositor space. + + For objects version 3 onwards, after all xdg_output properties have been + sent (when the object is created and when properties are updated), a + wl_output.done event is sent. This allows changes to the output + properties to be seen as atomic, even if they happen via multiple events. + + + + + Using this request a client can tell the server that it is not + going to use the xdg_output object anymore. + + + + + + The position event describes the location of the wl_output within + the global compositor space. + + The logical_position event is sent after creating an xdg_output + (see xdg_output_manager.get_xdg_output) and whenever the location + of the output changes within the global compositor space. + + + + + + + + The logical_size event describes the size of the output in the + global compositor space. + + For example, a surface without any buffer scale, transformation + nor rotation set, with the size matching the logical_size will + have the same size as the corresponding output when displayed. + + Most regular Wayland clients should not pay attention to the + logical size and would rather rely on xdg_shell interfaces. + + Some clients such as Xwayland, however, need this to configure + their surfaces in the global compositor space as the compositor + may apply a different scale from what is advertised by the output + scaling property (to achieve fractional scaling, for example). + + For example, for a wl_output mode 3840×2160 and a scale factor 2: + + - A compositor not scaling the surface buffers will advertise a + logical size of 3840×2160, + + - A compositor automatically scaling the surface buffers will + advertise a logical size of 1920×1080, + + - A compositor using a fractional scale of 1.5 will advertise a + logical size of 2560×1440. + + For example, for a wl_output mode 1920×1080 and a 90 degree rotation, + the compositor will advertise a logical size of 1080x1920. + + The logical_size event is sent after creating an xdg_output + (see xdg_output_manager.get_xdg_output) and whenever the logical + size of the output changes, either as a result of a change in the + applied scale or because of a change in the corresponding output + mode(see wl_output.mode) or transform (see wl_output.transform). + + + + + + + + This event is sent after all other properties of an xdg_output + have been sent. + + This allows changes to the xdg_output properties to be seen as + atomic, even if they happen via multiple events. + + For objects version 3 onwards, this event is deprecated. Compositors + are not required to send it anymore and must send wl_output.done + instead. + + + + + + + + Many compositors will assign names to their outputs, show them to the + user, allow them to be configured by name, etc. The client may wish to + know this name as well to offer the user similar behaviors. + + The naming convention is compositor defined, but limited to + alphanumeric characters and dashes (-). Each name is unique among all + wl_output globals, but if a wl_output global is destroyed the same name + may be reused later. The names will also remain consistent across + sessions with the same hardware and software configuration. + + Examples of names include 'HDMI-A-1', 'WL-1', 'X11-1', etc. However, do + not assume that the name is a reflection of an underlying DRM + connector, X11 connection, etc. + + The name event is sent after creating an xdg_output (see + xdg_output_manager.get_xdg_output). This event is only sent once per + xdg_output, and the name does not change over the lifetime of the + wl_output global. + + + + + + + Many compositors can produce human-readable descriptions of their + outputs. The client may wish to know this description as well, to + communicate the user for various purposes. + + The description is a UTF-8 string with no convention defined for its + contents. Examples might include 'Foocorp 11" Display' or 'Virtual X11 + output via :1'. + + The description event is sent after creating an xdg_output (see + xdg_output_manager.get_xdg_output) and whenever the description + changes. The description is optional, and may not be sent at all. + + For objects of version 2 and lower, this event is only sent once per + xdg_output, and the description does not change over the lifetime of + the wl_output global. + + + + + + diff --git a/sommelier/protocol/xdg-shell-unstable-v6.xml b/sommelier/protocol/xdg-shell.xml similarity index 71% rename from sommelier/protocol/xdg-shell-unstable-v6.xml rename to sommelier/protocol/xdg-shell.xml index 1c0f924..f7377a7 100644 --- a/sommelier/protocol/xdg-shell-unstable-v6.xml +++ b/sommelier/protocol/xdg-shell.xml @@ -1,11 +1,13 @@ - + Copyright © 2008-2013 Kristian Høgsberg Copyright © 2013 Rafael Antognolli Copyright © 2013 Jasper St. Pierre Copyright © 2010-2013 Intel Corporation + Copyright © 2015-2017 Samsung Electronics Co., Ltd + Copyright © 2015-2017 Red Hat Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), @@ -27,18 +29,19 @@ DEALINGS IN THE SOFTWARE. - + - xdg_shell allows clients to turn a wl_surface into a "real window" - which can be dragged, resized, stacked, and moved around by the - user. Everything about this interface is suited towards traditional - desktop environments. + The xdg_wm_base interface is exposed as a global object enabling clients + to turn their wl_surfaces into windows in a desktop environment. It + defines the basic functionality needed for clients and the compositor to + create windows that can be dragged, resized, maximized, etc, as well as + creating transient windows such as popup menus. + summary="xdg_wm_base was destroyed before children"/> - - Destroy this xdg_shell object. + + Destroy this xdg_wm_base object. - Destroying a bound xdg_shell object while there are surfaces - still alive created by this xdg_shell object instance is illegal + Destroying a bound xdg_wm_base object while there are surfaces + still alive created by this xdg_wm_base object instance is illegal and will result in a protocol error. @@ -65,7 +68,7 @@ surfaces relative to some parent surface. See the interface description and xdg_surface.get_popup for details. - + @@ -82,14 +85,14 @@ See the documentation of xdg_surface for more details about what an xdg_surface is and how it is used. - + A client must respond to a ping event with a pong request or - the client may be deemed unresponsive. See xdg_shell.ping. + the client may be deemed unresponsive. See xdg_wm_base.ping. @@ -98,7 +101,7 @@ The ping event asks the client if it's still alive. Pass the serial specified in the event back to the compositor by sending - a "pong" request back with the specified serial. See xdg_shell.ping. + a "pong" request back with the specified serial. See xdg_wm_base.pong. Compositors can use this to determine if the client is still alive. It's unspecified what will happen if the client doesn't @@ -106,13 +109,13 @@ try to respond in a reasonable amount of time. A compositor is free to ping in any way it wants, but a client must - always respond to any xdg_shell object it created. + always respond to any xdg_wm_base object it created. - + The xdg_positioner provides a collection of rules for the placement of a child surface relative to a parent surface. Rules can be defined to ensure @@ -162,13 +165,13 @@ Specify the anchor rectangle within the parent surface that the child surface will be placed relative to. The rectangle is relative to the window geometry as defined by xdg_surface.set_window_geometry of the - parent surface. The rectangle must be at least 1x1 large. + parent surface. When the xdg_positioner object is used to position a child surface, the anchor rectangle may not extend outside the window geometry of the positioned child's parent surface. - If a zero or negative size is set the invalid_input error is raised. + If a negative size is set the invalid_input error is raised. @@ -176,63 +179,54 @@ - - - - - - + + + + + + + + + + - - Defines a set of edges for the anchor rectangle. These are used to - derive an anchor point that the child surface will be positioned - relative to. If two orthogonal edges are specified (e.g. 'top' and - 'left'), then the anchor point will be the intersection of the edges - (e.g. the top left position of the rectangle); otherwise, the derived - anchor point will be centered on the specified edge, or in the center of - the anchor rectangle if no edge is specified. - - If two parallel anchor edges are specified (e.g. 'left' and 'right'), - the invalid_input error is raised. + + Defines the anchor point for the anchor rectangle. The specified anchor + is used derive an anchor point that the child surface will be + positioned relative to. If a corner anchor is set (e.g. 'top_left' or + 'bottom_right'), the anchor point will be at the specified corner; + otherwise, the derived anchor point will be centered on the specified + edge, or in the center of the anchor rectangle if no edge is specified. + summary="anchor"/> - - - - - - + + + + + + + + + + Defines in what direction a surface should be positioned, relative to - the anchor point of the parent surface. If two orthogonal gravities are - specified (e.g. 'bottom' and 'right'), then the child surface will be - placed in the specified direction; otherwise, the child surface will be - centered over the anchor point on any axis that had no gravity - specified. - - If two parallel gravities are specified (e.g. 'left' and 'right'), the - invalid_input error is raised. + the anchor point of the parent surface. If a corner gravity is + specified (e.g. 'bottom_right' or 'top_left'), then the child surface + will be placed towards the specified gravity; otherwise, the child + surface will be centered over the anchor point on any axis that had no + gravity specified. + summary="gravity direction"/> @@ -252,7 +246,7 @@ Don't alter the surface position even if it is constrained on some - axis, for example partially outside the edge of a monitor. + axis, for example partially outside the edge of an output. @@ -304,6 +298,10 @@ surface is constrained, the gravity is 'bottom' and the anchor is 'bottom', change the gravity to 'top' and the anchor to 'top'. + The adjusted position is calculated given the original anchor + rectangle and offset, but with the new flipped anchor and gravity + values. + If the adjusted position also ends up being constrained, the resulting position of the flip_y adjustment will be the one before the adjustment. @@ -359,9 +357,49 @@ + + + + + + When set reactive, the surface is reconstrained if the conditions used + for constraining changed, e.g. the parent window moved. + + If the conditions changed and the popup was reconstrained, an + xdg_popup.configure event is sent with updated geometry, followed by an + xdg_surface.configure event. + + + + + + Set the parent window geometry the compositor should use when + positioning the popup. The compositor may use this information to + determine the future state the popup should be constrained using. If + this doesn't match the dimension of the parent the popup is eventually + positioned against, the behavior is undefined. + + The arguments are given in the surface-local coordinate space. + + + + + + + + Set the serial of a xdg_surface.configure event this positioner will be + used in response to. The compositor may use this information together + with set_parent_size to determine what future state the popup should be + constrained using. + + + - + An interface that may be implemented by a wl_surface, for implementations that provide a desktop-style user interface. @@ -388,11 +426,25 @@ manipulate a buffer prior to the first xdg_surface.configure call must also be treated as errors. - For a surface to be mapped by the compositor, the following conditions - must be met: (1) the client has assigned a xdg_surface based role to the - surface, (2) the client has set and committed the xdg_surface state and - the role dependent state to the surface and (3) the client has committed a - buffer to the surface. + After creating a role-specific object and setting it up, the client must + perform an initial commit without any buffer attached. The compositor + will reply with an xdg_surface.configure event. The client must + acknowledge it and is then allowed to attach a buffer to map the surface. + + Mapping an xdg_surface-based role surface is defined as making it + possible for the surface to be shown by the compositor. Note that + a mapped surface is not guaranteed to be visible once it is mapped. + + For an xdg_surface to be mapped by the compositor, the following + conditions must be met: + (1) the client has assigned an xdg_surface-based role to the surface + (2) the client has set and committed the xdg_surface state and the + role-dependent state to the surface + (3) the client has committed a buffer to the surface + + A newly-unmapped surface is considered to have met condition (1) out + of the 3 required conditions for mapping a surface if its role surface + has not been destroyed. @@ -416,20 +468,23 @@ See the documentation of xdg_toplevel for more details about what an xdg_toplevel is and how it is used. - + - This creates an xdg_popup object for the given xdg_surface and gives the - associated wl_surface the xdg_popup role. + This creates an xdg_popup object for the given xdg_surface and gives + the associated wl_surface the xdg_popup role. + + If null is passed as a parent, a parent surface must be specified using + some other protocol, before committing the initial state. See the documentation of xdg_popup for more details about what an xdg_popup is and how it is used. - - - + + + @@ -442,6 +497,11 @@ The window geometry is double buffered, and will be applied at the time wl_surface.commit of the corresponding wl_surface is called. + When maintaining a position, the compositor should treat the (x, y) + coordinate of the window geometry as the top left corner of the window. + A client changing the (x, y) window geometry coordinate should in + general not alter the position of the window. + Once the window geometry of the surface is set, it is not possible to unset it, and it will remain the same until set_window_geometry is called again, even if a new subsurface or buffer is attached. @@ -511,36 +571,57 @@ + - + This interface defines an xdg_surface role which allows a surface to, among other things, set window-like properties such as maximize, fullscreen, and minimize, set application-specific metadata like title and id, and well as trigger user interactive operations such as interactive resize and move. + + Unmapping an xdg_toplevel means that the surface cannot be shown + by the compositor until it is explicitly mapped again. + All active operations (e.g., move, resize) are canceled and all + attributes (e.g. title, state, stacking, ...) are discarded for + an xdg_toplevel surface when it is unmapped. The xdg_toplevel returns to + the state it had right after xdg_surface.get_toplevel. The client + can re-map the toplevel by perfoming a commit without any buffer + attached, waiting for a configure event and handling it as usual (see + xdg_surface description). + + Attaching a null buffer to a toplevel unmaps the surface. - Unmap and destroy the window. The window will be effectively - hidden from the user's point of view, and all state like - maximization, fullscreen, and so on, will be lost. + This request destroys the role surface and unmaps the surface; + see "Unmapping" behavior in interface section for details. - Set the "parent" of this surface. This window should be stacked - above a parent. The parent surface must be mapped as long as this - surface is mapped. + Set the "parent" of this surface. This surface should be stacked + above the parent surface and all other ancestor surfaces. Parent windows should be set on dialogs, toolboxes, or other "auxiliary" surfaces, so that the parent is raised when the dialog is raised. + + Setting a null parent for a child window removes any parent-child + relationship for the child. Setting a null parent for a window which + currently has no parent is a no-op. + + If the parent is unmapped then its children are managed as + though the parent of the now-unmapped parent has become the + parent of this surface. If no parent exists for the now-unmapped + parent then the children are managed as though they have no + parent surface. - + @@ -573,6 +654,9 @@ For example, "org.freedesktop.FooViewer" where the .desktop file is "org.freedesktop.FooViewer.desktop". + Like other properties, a set_app_id request can be sent after the + xdg_toplevel has been mapped to update the property. + See the desktop-entry specification [0] for more details on application identifiers and how they relate to well-known D-Bus names and .desktop files. @@ -676,7 +760,7 @@ - + @@ -693,12 +777,18 @@ The surface is maximized. The window geometry specified in the configure event must be obeyed by the client. + + The client should draw without shadow or other + decoration outside of the window geometry. - The surface is fullscreen. The window geometry specified in the configure - event must be obeyed by the client. + The surface is fullscreen. The window geometry specified in the + configure event is a maximum; the client cannot resize beyond it. For + a surface to cover the whole fullscreened area, the geometry + dimensions must be obeyed by the client. For more details, see + xdg_toplevel.set_fullscreen. @@ -716,6 +806,30 @@ keyboard or pointer focus. + + + The window is currently in a tiled layout and the left edge is + considered to be adjacent to another part of the tiling grid. + + + + + The window is currently in a tiled layout and the right edge is + considered to be adjacent to another part of the tiling grid. + + + + + The window is currently in a tiled layout and the top edge is + considered to be adjacent to another part of the tiling grid. + + + + + The window is currently in a tiled layout and the bottom edge is + considered to be adjacent to another part of the tiling grid. + + @@ -805,12 +919,11 @@ Maximize the surface. After requesting that the surface should be maximized, the compositor - will respond by emitting a configure event with the "maximized" state - and the required window geometry. The client should then update its - content, drawing it in a maximized state, i.e. without shadow or other - decoration outside of the window geometry. The client must also - acknowledge the configure when committing the new content (see - ack_configure). + will respond by emitting a configure event. Whether this configure + actually sets the window maximized is subject to compositor policies. + The client must then update its content, drawing in the configured + state. The client must also acknowledge the configure when committing + the new content (see ack_configure). It is up to the compositor to decide how and where to maximize the surface, for example which output and what region of the screen should @@ -818,6 +931,10 @@ If the surface was already maximized, the compositor will still emit a configure event with the "maximized" state. + + If the surface is in a fullscreen state, this request has no direct + effect. It may alter the state the surface is returned to when + unmaximized unless overridden by the compositor. @@ -826,13 +943,13 @@ Unmaximize the surface. After requesting that the surface should be unmaximized, the compositor - will respond by emitting a configure event without the "maximized" - state. If available, the compositor will include the window geometry - dimensions the window had prior to being maximized in the configure - request. The client must then update its content, drawing it in a - regular state, i.e. potentially with shadow, etc. The client must also - acknowledge the configure when committing the new content (see - ack_configure). + will respond by emitting a configure event. Whether this actually + un-maximizes the window is subject to compositor policies. + If available and applicable, the compositor will include the window + geometry dimensions the window had prior to being maximized in the + configure event. The client must then update its content, drawing it in + the configured state. The client must also acknowledge the configure + when committing the new content (see ack_configure). It is up to the compositor to position the surface after it was unmaximized; usually the position the surface had before maximizing, if @@ -840,24 +957,63 @@ If the surface was already not maximized, the compositor will still emit a configure event without the "maximized" state. + + If the surface is in a fullscreen state, this request has no direct + effect. It may alter the state the surface is returned to when + unmaximized unless overridden by the compositor. - + Make the surface fullscreen. - You can specify an output that you would prefer to be fullscreen. - If this value is NULL, it's up to the compositor to choose which - display will be used to map this surface. + After requesting that the surface should be fullscreened, the + compositor will respond by emitting a configure event. Whether the + client is actually put into a fullscreen state is subject to compositor + policies. The client must also acknowledge the configure when + committing the new content (see ack_configure). + + The output passed by the request indicates the client's preference as + to which display it should be set fullscreen on. If this value is NULL, + it's up to the compositor to choose which display will be used to map + this surface. If the surface doesn't cover the whole output, the compositor will position the surface in the center of the output and compensate with - black borders filling the rest of the output. + with border fill covering the rest of the output. The content of the + border fill is undefined, but should be assumed to be in some way that + attempts to blend into the surrounding area (e.g. solid black). + + If the fullscreened surface is not opaque, the compositor must make + sure that other screen content not part of the same surface tree (made + up of subsurfaces, popups or similarly coupled surfaces) are not + visible below the fullscreened surface. - + + + + Make the surface no longer fullscreen. + + After requesting that the surface should be unfullscreened, the + compositor will respond by emitting a configure event. + Whether this actually removes the fullscreen state of the client is + subject to compositor policies. + + Making a surface unfullscreen sets states for the surface based on the following: + * the state(s) it may have had before becoming fullscreen + * any state(s) decided by the compositor + * any state(s) requested by the client while the surface was fullscreen + + The compositor may include the previous window geometry dimensions in + the configure event, if applicable. + + The client must also acknowledge the configure when committing the new + content (see ack_configure). + + @@ -913,7 +1069,7 @@ - + A popup surface is a short-lived, temporary surface. It can be used to implement for example menus, popovers, tooltips and other similar user @@ -931,21 +1087,12 @@ surface of their own is clicked should dismiss the popup using the destroy request. - The parent surface must have either the xdg_toplevel or xdg_popup surface - role. - A newly created xdg_popup will be stacked on top of all previously created xdg_popup surfaces associated with the same xdg_toplevel. The parent of an xdg_popup must be mapped (see the xdg_surface description) before the xdg_popup itself. - The x and y arguments passed when creating the popup object specify - where the top left of the popup should be placed, relative to the - local surface coordinates of the parent surface. See - xdg_surface.get_popup. An xdg_popup must intersect with or be at least - partially adjacent to its parent surface. - The client must call wl_surface.commit on the corresponding wl_surface for the xdg_popup state to take effect. @@ -1023,6 +1170,11 @@ The x and y arguments represent the position the popup was placed at given the xdg_positioner rule, relative to the upper left corner of the window geometry of the parent surface. + + For version 2 or older, the configure event for an xdg_popup is only + ever sent once for the initial configuration. Starting with version 3, + it may be sent again if the popup is setup with an xdg_positioner with + set_reactive requested, or in response to xdg_popup.reposition requests. @@ -1040,5 +1192,58 @@ + + + + + Reposition an already-mapped popup. The popup will be placed given the + details in the passed xdg_positioner object, and a + xdg_popup.repositioned followed by xdg_popup.configure and + xdg_surface.configure will be emitted in response. Any parameters set + by the previous positioner will be discarded. + + The passed token will be sent in the corresponding + xdg_popup.repositioned event. The new popup position will not take + effect until the corresponding configure event is acknowledged by the + client. See xdg_popup.repositioned for details. The token itself is + opaque, and has no other special meaning. + + If multiple reposition requests are sent, the compositor may skip all + but the last one. + + If the popup is repositioned in response to a configure event for its + parent, the client should send an xdg_positioner.set_parent_configure + and possibly a xdg_positioner.set_parent_size request to allow the + compositor to properly constrain the popup. + + If the popup is repositioned together with a parent that is being + resized, but not in response to a configure event, the client should + send a xdg_positioner.set_parent_size request. + + + + + + + + The repositioned event is sent as part of a popup configuration + sequence, together with xdg_popup.configure and lastly + xdg_surface.configure to notify the completion of a reposition request. + + The repositioned event is to notify about the completion of a + xdg_popup.reposition request. The token argument is the token passed + in the xdg_popup.reposition request. + + Immediately after this event is emitted, xdg_popup.configure and + xdg_surface.configure will be sent with the updated size and position, + as well as a new configure serial. + + The client should optionally update the content of the popup, but must + acknowledge the new popup configuration for the new position to take + effect. See xdg_surface.ack_configure for details. + + + + diff --git a/sommelier/scripts/buffer_stats.py b/sommelier/scripts/buffer_stats.py new file mode 100644 index 0000000..e037449 --- /dev/null +++ b/sommelier/scripts/buffer_stats.py @@ -0,0 +1,244 @@ +# Copyright 2021 The ChromiumOS Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Utility class for analyzing sommelier buffer statistics.""" + +import argparse +from collections import defaultdict +import sys + + +class Action(object): + """Hold data about sommelier buffer operations.""" + + __slots__ = ("type", "bid", "time") + + def __init__(self, action_type, bid, time): + self.type = action_type + self.bid = bid + self.time = time + + +class Stats: + """For processing text files of sommelier data and printing their stats.""" + + def __init__(self): + self.surfaces = defaultdict(list) + self.bid_sid = {} + self.sid_bid = {} + + def read(self, filename): + """Read in a sommelier-timing output file for later processing. + + Sample file: + Event, Type, Surface_ID, Buffer_ID, Time # header line 1 + 0 surface_attach 12 23 1612345678.987654321 # line 2 + 1 surface_commit 12 -1 1612345678.987654325 # lines 3, 4, ... + + Run sommelier with the timing-filename option to generate an event log: + sommelier -X --glamor --trace-system \ + --timing-filename=out.txt glxgears & + + Args: + filename (string): The path to the buffer time data. + """ + + with open(filename, "r") as f: + # Skip the header line. + f.readline() + for line in f: + try: + [_, action_type, sid, bid, timestamp] = line.split() + except ValueError: + continue + sid = int(sid) + bid = int(bid) + # use dictionary history to fill missing ids + if sid != -1 and bid != -1: + self.sid_bid[sid] = bid + self.bid_sid[bid] = sid + elif sid in self.sid_bid: + bid = self.sid_bid[sid] + elif bid in self.bid_sid: + sid = self.bid_sid[bid] + # convert string time into floating point time + time = float(timestamp) + + self.surfaces[sid].append(Action(action_type, bid, time)) + + def add(self, durr, tot, err): + """Kahan sum function to reduce floating point error. + + Args: + durr (float): The elapsed time between two actions. + tot (float): The summed total of elapsed times. + err (float): The accumulated error. + + Returns: + tot (int): the new summed total of elapsed times. + err (int): the new accumulated error. + """ + y = durr - err + t = tot + y + err = (t - tot) - y + tot = t + return tot, err + + def add_acr(self, sid, time, acr, idx): + """Update the sum/total, error, and count of each action type. + + Args: + sid (int): the id of the surface to display stats for. + time (float): the time the current action to place. + acr (dict): A dicitionary containing numerical info on accumulated stats. + idx (dict): A dictionary containing the last index of an action + """ + for action, pos in idx.items(): + if pos is not None: + prev_time = self.surfaces[sid][pos].time + x = action[0] # use first letter of action type in key names + tot, err = self.add(time - prev_time, acr[x], acr[x + "err"]) + acr[x] = tot + acr[x + "err"] = err + acr[x + "_count"] += 1 + + def print_stats(self, sid): + """Print the average times between calls to a surface. + + Args: + sid (int): the id of the surface to display stats for. + """ + + # to_attach - a dict used in calculating summed difference from any other + # call (a, c, r) to an "attach" call. + # a - attach, c - commit, r - release + # aerr - accumulated numerical error from summing differences. + # a_count - count of attach-attach differences so far. + to_attach = { + "a": 0, + "aerr": 0, + "a_count": 0, + "c": 0, + "cerr": 0, + "c_count": 0, + "r": 0, + "rerr": 0, + "r_count": 0, + } + to_commit = { + "a": 0, + "aerr": 0, + "a_count": 0, + "c": 0, + "cerr": 0, + "c_count": 0, + "r": 0, + "rerr": 0, + "r_count": 0, + } + to_release = { + "a": 0, + "aerr": 0, + "a_count": 0, + "c": 0, + "cerr": 0, + "c_count": 0, + "r": 0, + "rerr": 0, + "r_count": 0, + } + + # idx stores the last index of an attach, commit, and release call. + idx = {"attach": None, "commit": None, "release": None} + # For every action, calculate summed difference based on its type. + # e.g. for a commit action, calculate time between it and the previous + # attach, commit, and release call using to_commit. + for i, action in enumerate(self.surfaces[sid]): + time = action.time + + if action.type == "attach": + self.add_acr(sid, time, to_attach, idx) + idx["attach"] = i + elif action.type == "commit": + self.add_acr(sid, time, to_commit, idx) + idx["commit"] = i + elif action.type == "release": + self.add_acr(sid, time, to_release, idx) + idx["release"] = i + + print( + "attach-attach avg: ", + to_attach["a"] * 1000 / to_attach["a_count"], + " ms", + ) + print( + "commit-attach avg: ", + to_attach["c"] * 1000 / to_attach["c_count"], + " ms", + ) + print( + "release-attach avg: ", + to_attach["r"] * 1000 / to_attach["r_count"], + " ms", + ) + print() + print( + "commit-commit avg: ", + to_commit["c"] * 1000 / to_commit["c_count"], + " ms", + ) + print( + "attach-commit avg: ", + to_commit["a"] * 1000 / to_commit["a_count"], + " ms", + ) + print( + "release-commit avg: ", + to_commit["r"] * 1000 / to_commit["r_count"], + " ms", + ) + print() + print( + "release-release avg: ", + to_release["r"] * 1000 / to_release["r_count"], + " ms", + ) + print( + "attach-release avg: ", + to_release["a"] * 1000 / to_release["a_count"], + " ms", + ) + print( + "commit-release avg: ", + to_release["c"] * 1000 / to_release["c_count"], + " ms", + ) + + +def main(args): + parser = argparse.ArgumentParser( + description="Compute average of interprocess times" + ) + parser.add_argument("file", type=str, help="the timing log file to process") + parser.add_argument( + "--surface_id", + type=int, + help="the id of the surface to compute averages on", + ) + args = parser.parse_args() + log = args.file + sid = args.surface_id + stats = Stats() + stats.read(log) + # Print buffer stats for the sid with the most number of actions. + if not sid: + sid = next(iter(stats.surfaces.keys())) + for key in stats.surfaces: + if len(stats.surfaces[key]) > len(stats.surfaces[sid]): + sid = key + stats.print_stats(sid) + + +if __name__ == "__main__": + main(sys.argv[1:]) diff --git a/sommelier/scripts/get_frame_summary.py b/sommelier/scripts/get_frame_summary.py new file mode 100755 index 0000000..e5b5b66 --- /dev/null +++ b/sommelier/scripts/get_frame_summary.py @@ -0,0 +1,279 @@ +#!/usr/bin/env python3 +# Copyright 2022 The ChromiumOS Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +"""Summarizes Sommelier timing information.""" + +import argparse +from enum import Enum +import statistics +from typing import NamedTuple + + +_MS_PER_SEC = 1000 +_US_PER_SEC = 1000000 +# Floating point error bounds +_FP_ERROR = 0.01 + + +class EventType(Enum): + """Wayland event type.""" + + COMMIT = 1 + ATTACH = 2 + RELEASE = 3 + UNKNOWN = 4 + + +class EventInfo(NamedTuple): + """Stores information of an event.""" + + event_type: EventType + surface_id: int + buffer_id: int + time: float + + +def parse_event_type(event_type): + EVENT_MAP = { + "a": EventType.ATTACH, + "c": EventType.COMMIT, + "r": EventType.RELEASE, + } + return EVENT_MAP.get(event_type, EventType.UNKNOWN) + + +class FrameLog: + """Manages access to the Sommelier timing logs.""" + + def __init__(self, filename): + """Parse Sommelier timing log. + + Format of log (header line might be truncated): + Type Surface_ID Buffer_ID Delta_Time # header line 1 + a 12 20 4237.44 # line 2 + .... + EndTime 3972 1655330324.7 # last line + Last line format: (EndTime, last event id, time since epoch (s)) + """ + self.frame_log = [] + self.surfaces = set() + with open(filename, "r") as f: + lines = f.read().splitlines() + total_delta_time = 0 + last_line = lines[-1].split(" ") + if len(last_line) != 3 or last_line[0] != "EndTime": + print(f"Invalid EndTime: {lines[-1]}") + return + self.end_time = float(last_line[2]) + for l in reversed(lines[1:-1]): + line = l.rstrip().split(" ") + # Skip parsing line that is improperly formatted + if len(line) != 4: + continue + total_delta_time += float(line[3]) / _US_PER_SEC + surface_id = int(line[1]) + info = EventInfo( + event_type=parse_event_type(line[0]), + surface_id=surface_id, + buffer_id=int(line[2]), + time=self.end_time - total_delta_time, + ) + self.frame_log.append(info) + self.surfaces.add(surface_id) + + def get_target_ft(self, target_fps, avg_fps): + """Outputs target frame time given a target fps. + + If target_fps is None, determine automatically based on + average FPS. + + Args: + target_fps: Target FPS (30, 60, None). + avg_fps: Average FPS over time window. + """ + if not target_fps: + # determines appropriate target FPS by finding whether the + # average is closer to 30 or 60. + # TODO(asriniva): Revisit this methodology. What should a title + # averaging 31-33 FPS target? Non 30/60 FPS targets? + target_fps = min([30, 60], key=lambda x: abs(x - avg_fps)) + # Acceptable frame time ranges, based on Battlestar's metrics. + # The +/-3 bounds do not scale with FPS (small variance for high FPS, + # large variance for low FPS) but can account for variability in + # hardware. + return [_MS_PER_SEC / target_fps - 3, _MS_PER_SEC / target_fps + 3] + + def output_fps(self, surface, windows, max_ft_ms, target_fps): + """Outputs the summarized fps information based on frame log. + + Args: + surface: Surface ID + windows: List of time windows (in seconds) to summarize. + max_ft_ms: Max frame time threshold (ms). + target_fps: Target FPS, either 30, 60, or + None (automatically determined). + """ + max_frame_ms = 0 + # only check for commit events on the given surface + # events are in reverse chronological order + events = [ + e + for e in self.frame_log + if e.surface_id == surface and e.event_type == EventType.COMMIT + ] + if not events: + print(f"No commit events found for surface {surface}\n") + return + total_sec = self.end_time - events[-1].time + ft_target_ms = self.get_target_ft(target_fps, len(events) / total_sec) + for w_sec in windows + [total_sec]: + # num frames in acceptable range + target_frames = 0 + # num frames exceeding max_ft_ms + max_ft_events = 0 + prev_sec = self.end_time + frame_count = 0 + frames_ms = [] + for event in events: + frame_ms = (prev_sec - event.time) * _MS_PER_SEC + frames_ms.append(frame_ms) + max_frame_ms = max(max_frame_ms, frame_ms) + if ft_target_ms[0] < frame_ms < ft_target_ms[1]: + target_frames += 1 + if frame_ms > max_ft_ms: + max_ft_events += 1 + current_sec = self.end_time - event.time + frame_count += 1 + # handles floating point error in the case when + # w_sec == total_sec + if current_sec > w_sec - _FP_ERROR: + print(f"Summary for last {w_sec} seconds") + print("-------------------------------") + print(f"FPS: {frame_count / current_sec}") + print(f"Max frame time: {max_frame_ms} ms") + print(f"Frame count: {frame_count} frames") + print( + f"Percentage frames within acceptable target " + f"{ft_target_ms} ms: " + f"{target_frames * 100/frame_count}%" + ) + if len(frames_ms) > 1: + c_var = statistics.stdev(frames_ms) / statistics.mean( + frames_ms + ) + print(f"Coefficient of variation: {c_var}") + print( + f"Frames exceeding max frame time threshold" + f" {max_ft_ms} ms:" + f" {max_ft_events} frames" + ) + print() + break + prev_sec = event.time + print() + + def output_fps_sliding(self, surface, window, max_ft_ms): + """Outputs the summarized fps information based on frame log. + + Args: + surface: Surface ID + window: Window size (in num of frames). + max_ft_ms: Max frame time threshold (ms). + target_fps: Target FPS, either 30, 60, or + None (automatically determined). + """ + print(f"Sliding window aggregates for {window} events") + print("-------------------------------") + + # only check for commit events on the given surface + # events are in reverse chronological order + events = [ + e + for e in self.frame_log + if e.surface_id == surface and e.event_type == EventType.COMMIT + ] + if not events: + print(f"No commit events found for surface {surface}\n") + return + # For a sliding window, aggregate the following: + # cvars: Coefficient of variation over window + # max_ft_events: Number of frames over max_ft_ms + cvars = [] + max_ft_events = [] + for i in range(window, len(events)): + sl_window = events[i - window : i] + frames_ms = [ + (sl_window[x].time - sl_window[x + 1].time) * _MS_PER_SEC + for x in range(window - 1) + ] + + max_fts = sum([1 for x in frames_ms if x > max_ft_ms]) + c_var = statistics.stdev(frames_ms) / statistics.mean(frames_ms) + cvars.append(c_var) + max_ft_events.append(max_fts) + if len(cvars) > 1: + print( + "Arithmetic mean of coefficient of variation:", + statistics.fmean(cvars), + ) + print( + "Geometric mean of coefficient of variation:", + statistics.geometric_mean(cvars), + ) + if len(max_ft_events) > 1: + print( + f"Average number of frame time events over threshold" + f" {max_ft_ms} ms:", + statistics.mean(max_ft_events), + ) + print() + + print() + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="Return frame summary based on Sommelier timing log." + ) + + parser.add_argument("file", help="Filename of timing log") + parser.add_argument( + "--windows", + action="extend", + type=int, + nargs="+", + help="Time windows for summary (in seconds)", + default=[10, 60, 300], + ) + parser.add_argument( + "--target-fps", type=int, help="Target FPS", default=None + ) + parser.add_argument( + "--max-frame-time", + type=float, + help="Max frame time threshold (in milliseconds)", + default=200, + ) + parser.add_argument( + "--sliding", + type=int, + help="Use sliding window with size in number of frames", + default=300, + ) + args = parser.parse_args() + if args.target_fps and args.target_fps < 20: + parser.error("Choose target FPS above 20 FPS") + log = FrameLog(args.file) + for s in log.surfaces: + print(f"Summary for surface {s}") + print("-------------------------------") + log.output_fps_sliding( + s, max_ft_ms=args.max_frame_time, window=args.sliding + ) + log.output_fps( + s, + windows=sorted(args.windows), + max_ft_ms=args.max_frame_time, + target_fps=args.target_fps, + ) diff --git a/sommelier/sommelier-compositor.c b/sommelier/sommelier-compositor.c deleted file mode 100644 index 327f99a..0000000 --- a/sommelier/sommelier-compositor.c +++ /dev/null @@ -1,888 +0,0 @@ -// Copyright 2018 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "sommelier.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "drm-server-protocol.h" -#include "linux-dmabuf-unstable-v1-client-protocol.h" -#include "viewporter-client-protocol.h" - -#define MIN_SIZE (INT_MIN / 10) -#define MAX_SIZE (INT_MAX / 10) - -#define DMA_BUF_SYNC_READ (1 << 0) -#define DMA_BUF_SYNC_WRITE (2 << 0) -#define DMA_BUF_SYNC_RW (DMA_BUF_SYNC_READ | DMA_BUF_SYNC_WRITE) -#define DMA_BUF_SYNC_START (0 << 2) -#define DMA_BUF_SYNC_END (1 << 2) - -#define DMA_BUF_BASE 'b' -#define DMA_BUF_IOCTL_SYNC _IOW(DMA_BUF_BASE, 0, struct dma_buf_sync) - -struct sl_host_compositor { - struct sl_compositor* compositor; - struct wl_resource* resource; - struct wl_compositor* proxy; -}; - -struct sl_output_buffer { - struct wl_list link; - uint32_t width; - uint32_t height; - uint32_t format; - struct wl_buffer* internal; - struct sl_mmap* mmap; - struct pixman_region32 damage; - struct sl_host_surface* surface; -}; - -struct dma_buf_sync { - __u64 flags; -}; - -static void sl_dmabuf_sync(int fd, __u64 flags) { - struct dma_buf_sync sync = {0}; - int rv; - - sync.flags = flags; - do { - rv = ioctl(fd, DMA_BUF_IOCTL_SYNC, &sync); - } while (rv == -1 && errno == EINTR); -} - -static void sl_dmabuf_begin_write(int fd) { - sl_dmabuf_sync(fd, DMA_BUF_SYNC_START | DMA_BUF_SYNC_WRITE); -} - -static void sl_dmabuf_end_write(int fd) { - sl_dmabuf_sync(fd, DMA_BUF_SYNC_END | DMA_BUF_SYNC_WRITE); -} - -static void sl_virtwl_dmabuf_sync(int fd, __u32 flags) { - struct virtwl_ioctl_dmabuf_sync sync = {0}; - int rv; - - sync.flags = flags; - rv = ioctl(fd, VIRTWL_IOCTL_DMABUF_SYNC, &sync); - assert(!rv); - UNUSED(rv); -} - -static void sl_virtwl_dmabuf_begin_write(int fd) { - sl_virtwl_dmabuf_sync(fd, DMA_BUF_SYNC_START | DMA_BUF_SYNC_WRITE); -} - -static void sl_virtwl_dmabuf_end_write(int fd) { - sl_virtwl_dmabuf_sync(fd, DMA_BUF_SYNC_END | DMA_BUF_SYNC_WRITE); -} - -static uint32_t sl_gbm_format_for_shm_format(uint32_t format) { - switch (format) { - case WL_SHM_FORMAT_NV12: - return GBM_FORMAT_NV12; - case WL_SHM_FORMAT_RGB565: - return GBM_FORMAT_RGB565; - case WL_SHM_FORMAT_ARGB8888: - return GBM_FORMAT_ARGB8888; - case WL_SHM_FORMAT_ABGR8888: - return GBM_FORMAT_ABGR8888; - case WL_SHM_FORMAT_XRGB8888: - return GBM_FORMAT_XRGB8888; - case WL_SHM_FORMAT_XBGR8888: - return GBM_FORMAT_XBGR8888; - } - assert(0); - return 0; -} - -static uint32_t sl_drm_format_for_shm_format(int format) { - switch (format) { - case WL_SHM_FORMAT_NV12: - return WL_DRM_FORMAT_NV12; - case WL_SHM_FORMAT_RGB565: - return WL_DRM_FORMAT_RGB565; - case WL_SHM_FORMAT_ARGB8888: - return WL_DRM_FORMAT_ARGB8888; - case WL_SHM_FORMAT_ABGR8888: - return WL_DRM_FORMAT_ABGR8888; - case WL_SHM_FORMAT_XRGB8888: - return WL_DRM_FORMAT_XRGB8888; - case WL_SHM_FORMAT_XBGR8888: - return WL_DRM_FORMAT_XBGR8888; - } - assert(0); - return 0; -} - -static void sl_output_buffer_destroy(struct sl_output_buffer* buffer) { - wl_buffer_destroy(buffer->internal); - sl_mmap_unref(buffer->mmap); - pixman_region32_fini(&buffer->damage); - wl_list_remove(&buffer->link); - free(buffer); -} - -static void sl_output_buffer_release(void* data, struct wl_buffer* buffer) { - struct sl_output_buffer* output_buffer = wl_buffer_get_user_data(buffer); - struct sl_host_surface* host_surface = output_buffer->surface; - - wl_list_remove(&output_buffer->link); - wl_list_insert(&host_surface->released_buffers, &output_buffer->link); -} - -static const struct wl_buffer_listener sl_output_buffer_listener = { - sl_output_buffer_release}; - -static void sl_host_surface_destroy(struct wl_client* client, - struct wl_resource* resource) { - wl_resource_destroy(resource); -} - -static void sl_host_surface_attach(struct wl_client* client, - struct wl_resource* resource, - struct wl_resource* buffer_resource, - int32_t x, - int32_t y) { - struct sl_host_surface* host = wl_resource_get_user_data(resource); - struct sl_host_buffer* host_buffer = - buffer_resource ? wl_resource_get_user_data(buffer_resource) : NULL; - struct wl_buffer* buffer_proxy = NULL; - struct sl_window* window; - double scale = host->ctx->scale; - - host->current_buffer = NULL; - if (host->contents_shm_mmap) { - sl_mmap_unref(host->contents_shm_mmap); - host->contents_shm_mmap = NULL; - } - - if (host_buffer) { - host->contents_width = host_buffer->width; - host->contents_height = host_buffer->height; - buffer_proxy = host_buffer->proxy; - if (host_buffer->shm_mmap) - host->contents_shm_mmap = sl_mmap_ref(host_buffer->shm_mmap); - } - - if (host->contents_shm_mmap) { - while (!wl_list_empty(&host->released_buffers)) { - host->current_buffer = wl_container_of(host->released_buffers.next, - host->current_buffer, link); - - if (host->current_buffer->width == host_buffer->width && - host->current_buffer->height == host_buffer->height && - host->current_buffer->format == host_buffer->shm_format) { - break; - } - - sl_output_buffer_destroy(host->current_buffer); - host->current_buffer = NULL; - } - - // Allocate new output buffer. - if (!host->current_buffer) { - size_t width = host_buffer->width; - size_t height = host_buffer->height; - uint32_t shm_format = host_buffer->shm_format; - size_t bpp = sl_shm_bpp_for_shm_format(shm_format); - size_t num_planes = sl_shm_num_planes_for_shm_format(shm_format); - - host->current_buffer = malloc(sizeof(struct sl_output_buffer)); - assert(host->current_buffer); - wl_list_insert(&host->released_buffers, &host->current_buffer->link); - host->current_buffer->width = width; - host->current_buffer->height = height; - host->current_buffer->format = shm_format; - host->current_buffer->surface = host; - pixman_region32_init_rect(&host->current_buffer->damage, 0, 0, MAX_SIZE, - MAX_SIZE); - - switch (host->ctx->shm_driver) { - case SHM_DRIVER_DMABUF: { - struct zwp_linux_buffer_params_v1* buffer_params; - struct gbm_bo* bo; - int stride0; - int fd; - - bo = gbm_bo_create(host->ctx->gbm, width, height, - sl_gbm_format_for_shm_format(shm_format), - GBM_BO_USE_SCANOUT | GBM_BO_USE_LINEAR); - stride0 = gbm_bo_get_stride(bo); - fd = gbm_bo_get_fd(bo); - - buffer_params = zwp_linux_dmabuf_v1_create_params( - host->ctx->linux_dmabuf->internal); - zwp_linux_buffer_params_v1_add(buffer_params, fd, 0, 0, stride0, 0, - 0); - host->current_buffer->internal = - zwp_linux_buffer_params_v1_create_immed( - buffer_params, width, height, - sl_drm_format_for_shm_format(shm_format), 0); - zwp_linux_buffer_params_v1_destroy(buffer_params); - - host->current_buffer->mmap = sl_mmap_create( - fd, height * stride0, bpp, 1, 0, stride0, 0, 0, 1, 0); - host->current_buffer->mmap->begin_write = sl_dmabuf_begin_write; - host->current_buffer->mmap->end_write = sl_dmabuf_end_write; - - gbm_bo_destroy(bo); - } break; - case SHM_DRIVER_VIRTWL: { - size_t size = host_buffer->shm_mmap->size; - struct virtwl_ioctl_new ioctl_new = {.type = VIRTWL_IOCTL_NEW_ALLOC, - .fd = -1, - .flags = 0, - .size = size}; - struct wl_shm_pool* pool; - int rv; - - rv = ioctl(host->ctx->virtwl_fd, VIRTWL_IOCTL_NEW, &ioctl_new); - assert(rv == 0); - UNUSED(rv); - - pool = - wl_shm_create_pool(host->ctx->shm->internal, ioctl_new.fd, size); - host->current_buffer->internal = wl_shm_pool_create_buffer( - pool, 0, width, height, host_buffer->shm_mmap->stride[0], - shm_format); - wl_shm_pool_destroy(pool); - - host->current_buffer->mmap = sl_mmap_create( - ioctl_new.fd, size, bpp, num_planes, 0, - host_buffer->shm_mmap->stride[0], - host_buffer->shm_mmap->offset[1] - - host_buffer->shm_mmap->offset[0], - host_buffer->shm_mmap->stride[1], host_buffer->shm_mmap->y_ss[0], - host_buffer->shm_mmap->y_ss[1]); - } break; - case SHM_DRIVER_VIRTWL_DMABUF: { - uint32_t drm_format = sl_drm_format_for_shm_format(shm_format); - struct virtwl_ioctl_new ioctl_new = { - .type = VIRTWL_IOCTL_NEW_DMABUF, - .fd = -1, - .flags = 0, - .dmabuf = { - .width = width, .height = height, .format = drm_format}}; - struct zwp_linux_buffer_params_v1* buffer_params; - size_t size; - int rv; - - rv = ioctl(host->ctx->virtwl_fd, VIRTWL_IOCTL_NEW, &ioctl_new); - if (rv) { - fprintf(stderr, "error: virtwl dmabuf allocation failed: %s\n", - strerror(errno)); - _exit(EXIT_FAILURE); - } - - size = ioctl_new.dmabuf.stride0 * height; - buffer_params = zwp_linux_dmabuf_v1_create_params( - host->ctx->linux_dmabuf->internal); - zwp_linux_buffer_params_v1_add(buffer_params, ioctl_new.fd, 0, - ioctl_new.dmabuf.offset0, - ioctl_new.dmabuf.stride0, 0, 0); - if (num_planes > 1) { - zwp_linux_buffer_params_v1_add(buffer_params, ioctl_new.fd, 1, - ioctl_new.dmabuf.offset1, - ioctl_new.dmabuf.stride1, 0, 0); - size = MAX(size, ioctl_new.dmabuf.offset1 + - ioctl_new.dmabuf.stride1 * height / - host_buffer->shm_mmap->y_ss[1]); - } - host->current_buffer->internal = - zwp_linux_buffer_params_v1_create_immed(buffer_params, width, - height, drm_format, 0); - zwp_linux_buffer_params_v1_destroy(buffer_params); - - host->current_buffer->mmap = sl_mmap_create( - ioctl_new.fd, size, bpp, num_planes, ioctl_new.dmabuf.offset0, - ioctl_new.dmabuf.stride0, ioctl_new.dmabuf.offset1, - ioctl_new.dmabuf.stride1, host_buffer->shm_mmap->y_ss[0], - host_buffer->shm_mmap->y_ss[1]); - host->current_buffer->mmap->begin_write = - sl_virtwl_dmabuf_begin_write; - host->current_buffer->mmap->end_write = sl_virtwl_dmabuf_end_write; - } break; - } - - assert(host->current_buffer->internal); - assert(host->current_buffer->mmap); - - wl_buffer_set_user_data(host->current_buffer->internal, - host->current_buffer); - wl_buffer_add_listener(host->current_buffer->internal, - &sl_output_buffer_listener, host->current_buffer); - } - } - - x /= scale; - y /= scale; - - // TODO(davidriley): This should be done in the commit. - if (host_buffer && host_buffer->sync_point) { - host_buffer->sync_point->sync(host->ctx, host_buffer->sync_point); - } - - if (host->current_buffer) { - assert(host->current_buffer->internal); - wl_surface_attach(host->proxy, host->current_buffer->internal, x, y); - } else { - wl_surface_attach(host->proxy, buffer_proxy, x, y); - } - - wl_list_for_each(window, &host->ctx->windows, link) { - if (window->host_surface_id == wl_resource_get_id(resource)) { - while (sl_process_pending_configure_acks(window, host)) - continue; - - break; - } - } -} - -static void sl_host_surface_damage(struct wl_client* client, - struct wl_resource* resource, - int32_t x, - int32_t y, - int32_t width, - int32_t height) { - struct sl_host_surface* host = wl_resource_get_user_data(resource); - double scale = host->ctx->scale; - struct sl_output_buffer* buffer; - int64_t x1, y1, x2, y2; - - wl_list_for_each(buffer, &host->busy_buffers, link) { - pixman_region32_union_rect(&buffer->damage, &buffer->damage, x, y, width, - height); - } - wl_list_for_each(buffer, &host->released_buffers, link) { - pixman_region32_union_rect(&buffer->damage, &buffer->damage, x, y, width, - height); - } - - x1 = x; - y1 = y; - x2 = x1 + width; - y2 = y1 + height; - - // Enclosing rect after scaling and outset by one pixel to account for - // potential filtering. - x1 = MAX(MIN_SIZE, x1 - 1) / scale; - y1 = MAX(MIN_SIZE, y1 - 1) / scale; - x2 = ceil(MIN(x2 + 1, MAX_SIZE) / scale); - y2 = ceil(MIN(y2 + 1, MAX_SIZE) / scale); - - wl_surface_damage(host->proxy, x1, y1, x2 - x1, y2 - y1); -} - -static void sl_frame_callback_done(void* data, - struct wl_callback* callback, - uint32_t time) { - struct sl_host_callback* host = wl_callback_get_user_data(callback); - - wl_callback_send_done(host->resource, time); - wl_resource_destroy(host->resource); -} - -static const struct wl_callback_listener sl_frame_callback_listener = { - sl_frame_callback_done}; - -static void sl_host_callback_destroy(struct wl_resource* resource) { - struct sl_host_callback* host = wl_resource_get_user_data(resource); - - wl_callback_destroy(host->proxy); - wl_resource_set_user_data(resource, NULL); - free(host); -} - -static void sl_host_surface_frame(struct wl_client* client, - struct wl_resource* resource, - uint32_t callback) { - struct sl_host_surface* host = wl_resource_get_user_data(resource); - struct sl_host_callback* host_callback; - - host_callback = malloc(sizeof(*host_callback)); - assert(host_callback); - - host_callback->resource = - wl_resource_create(client, &wl_callback_interface, 1, callback); - wl_resource_set_implementation(host_callback->resource, NULL, host_callback, - sl_host_callback_destroy); - host_callback->proxy = wl_surface_frame(host->proxy); - wl_callback_set_user_data(host_callback->proxy, host_callback); - wl_callback_add_listener(host_callback->proxy, &sl_frame_callback_listener, - host_callback); -} - -static void sl_host_surface_set_opaque_region( - struct wl_client* client, - struct wl_resource* resource, - struct wl_resource* region_resource) { - struct sl_host_surface* host = wl_resource_get_user_data(resource); - struct sl_host_region* host_region = - region_resource ? wl_resource_get_user_data(region_resource) : NULL; - - wl_surface_set_opaque_region(host->proxy, - host_region ? host_region->proxy : NULL); -} - -static void sl_host_surface_set_input_region( - struct wl_client* client, - struct wl_resource* resource, - struct wl_resource* region_resource) { - struct sl_host_surface* host = wl_resource_get_user_data(resource); - struct sl_host_region* host_region = - region_resource ? wl_resource_get_user_data(region_resource) : NULL; - - wl_surface_set_input_region(host->proxy, - host_region ? host_region->proxy : NULL); -} - -static void sl_host_surface_commit(struct wl_client* client, - struct wl_resource* resource) { - struct sl_host_surface* host = wl_resource_get_user_data(resource); - struct sl_viewport* viewport = NULL; - struct sl_window* window; - - if (!wl_list_empty(&host->contents_viewport)) - viewport = wl_container_of(host->contents_viewport.next, viewport, link); - - if (host->contents_shm_mmap) { - uint8_t* src_addr = host->contents_shm_mmap->addr; - uint8_t* dst_addr = host->current_buffer->mmap->addr; - size_t* src_offset = host->contents_shm_mmap->offset; - size_t* dst_offset = host->current_buffer->mmap->offset; - size_t* src_stride = host->contents_shm_mmap->stride; - size_t* dst_stride = host->current_buffer->mmap->stride; - size_t* y_ss = host->contents_shm_mmap->y_ss; - size_t bpp = host->contents_shm_mmap->bpp; - size_t num_planes = host->contents_shm_mmap->num_planes; - double contents_scale_x = host->contents_scale; - double contents_scale_y = host->contents_scale; - double contents_offset_x = 0.0; - double contents_offset_y = 0.0; - pixman_box32_t* rect; - int n; - - // Determine scale and offset for damage based on current viewport. - if (viewport) { - double contents_width = host->contents_width; - double contents_height = host->contents_height; - - if (viewport->src_x >= 0 && viewport->src_y >= 0) { - contents_offset_x = wl_fixed_to_double(viewport->src_x); - contents_offset_y = wl_fixed_to_double(viewport->src_y); - } - - if (viewport->dst_width > 0 && viewport->dst_height > 0) { - contents_scale_x *= contents_width / viewport->dst_width; - contents_scale_y *= contents_height / viewport->dst_height; - - // Take source rectangle into account when both destionation size and - // source rectangle are set. If only source rectangle is set, then - // it determines the surface size so it can be ignored. - if (viewport->src_width >= 0 && viewport->src_height >= 0) { - contents_scale_x *= - wl_fixed_to_double(viewport->src_width) / contents_width; - contents_scale_y *= - wl_fixed_to_double(viewport->src_height) / contents_height; - } - } - } - - if (host->current_buffer->mmap->begin_write) - host->current_buffer->mmap->begin_write(host->current_buffer->mmap->fd); - - rect = pixman_region32_rectangles(&host->current_buffer->damage, &n); - while (n--) { - int32_t x1, y1, x2, y2; - - // Enclosing rect after applying scale and offset. - x1 = rect->x1 * contents_scale_x + contents_offset_x; - y1 = rect->y1 * contents_scale_y + contents_offset_y; - x2 = rect->x2 * contents_scale_x + contents_offset_x + 0.5; - y2 = rect->y2 * contents_scale_y + contents_offset_y + 0.5; - - x1 = MAX(0, x1); - y1 = MAX(0, y1); - x2 = MIN(host->contents_width, x2); - y2 = MIN(host->contents_height, y2); - - if (x1 < x2 && y1 < y2) { - size_t i; - - for (i = 0; i < num_planes; ++i) { - uint8_t* src_base = src_addr + src_offset[i]; - uint8_t* dst_base = dst_addr + dst_offset[i]; - uint8_t* src = src_base + y1 * src_stride[i] + x1 * bpp; - uint8_t* dst = dst_base + y1 * dst_stride[i] + x1 * bpp; - int32_t width = x2 - x1; - int32_t height = (y2 - y1) / y_ss[i]; - size_t bytes = width * bpp; - - while (height--) { - memcpy(dst, src, bytes); - dst += dst_stride[i]; - src += src_stride[i]; - } - } - } - - ++rect; - } - - if (host->current_buffer->mmap->end_write) - host->current_buffer->mmap->end_write(host->current_buffer->mmap->fd); - - pixman_region32_clear(&host->current_buffer->damage); - - wl_list_remove(&host->current_buffer->link); - wl_list_insert(&host->busy_buffers, &host->current_buffer->link); - } - - if (host->contents_width && host->contents_height) { - double scale = host->ctx->scale * host->contents_scale; - - if (host->viewport) { - int width = host->contents_width; - int height = host->contents_height; - - // We need to take the client's viewport into account while still - // making sure our scale is accounted for. - if (viewport) { - if (viewport->src_x >= 0 && viewport->src_y >= 0 && - viewport->src_width >= 0 && viewport->src_height >= 0) { - wp_viewport_set_source(host->viewport, viewport->src_x, - viewport->src_y, viewport->src_width, - viewport->src_height); - - // If the source rectangle is set and the destination size is not - // set, then src_width and src_height should be integers, and the - // surface size becomes the source rectangle size. - width = wl_fixed_to_int(viewport->src_width); - height = wl_fixed_to_int(viewport->src_height); - } - - // Use destination size as surface size when set. - if (viewport->dst_width >= 0 && viewport->dst_height >= 0) { - width = viewport->dst_width; - height = viewport->dst_height; - } - } - - wp_viewport_set_destination(host->viewport, ceil(width / scale), - ceil(height / scale)); - } else { - wl_surface_set_buffer_scale(host->proxy, scale); - } - } - - // No need to defer client commits if surface has a role. E.g. is a cursor - // or shell surface. - if (host->has_role) { - wl_surface_commit(host->proxy); - - // GTK determines the scale based on the output the surface has entered. - // If the surface has not entered any output, then have it enter the - // internal output. TODO(reveman): Remove this when surface-output tracking - // has been implemented in Chrome. - if (!host->has_output) { - struct sl_host_output* output; - - wl_list_for_each(output, &host->ctx->host_outputs, link) { - if (output->internal) { - wl_surface_send_enter(host->resource, output->resource); - host->has_output = 1; - break; - } - } - } - } else { - // Commit if surface is associated with a window. Otherwise, defer - // commit until window is created. - wl_list_for_each(window, &host->ctx->windows, link) { - if (window->host_surface_id == wl_resource_get_id(resource)) { - if (window->xdg_surface) { - wl_surface_commit(host->proxy); - if (host->contents_width && host->contents_height) - window->realized = 1; - } - break; - } - } - } - - if (host->contents_shm_mmap) { - if (host->contents_shm_mmap->buffer_resource) - wl_buffer_send_release(host->contents_shm_mmap->buffer_resource); - sl_mmap_unref(host->contents_shm_mmap); - host->contents_shm_mmap = NULL; - } -} - -static void sl_host_surface_set_buffer_transform(struct wl_client* client, - struct wl_resource* resource, - int32_t transform) { - struct sl_host_surface* host = wl_resource_get_user_data(resource); - - wl_surface_set_buffer_transform(host->proxy, transform); -} - -static void sl_host_surface_set_buffer_scale(struct wl_client* client, - struct wl_resource* resource, - int32_t scale) { - struct sl_host_surface* host = wl_resource_get_user_data(resource); - - host->contents_scale = scale; -} - -static void sl_host_surface_damage_buffer(struct wl_client* client, - struct wl_resource* resource, - int32_t x, - int32_t y, - int32_t width, - int32_t height) { - assert(0); -} - -static const struct wl_surface_interface sl_surface_implementation = { - sl_host_surface_destroy, - sl_host_surface_attach, - sl_host_surface_damage, - sl_host_surface_frame, - sl_host_surface_set_opaque_region, - sl_host_surface_set_input_region, - sl_host_surface_commit, - sl_host_surface_set_buffer_transform, - sl_host_surface_set_buffer_scale, - sl_host_surface_damage_buffer}; - -static void sl_destroy_host_surface(struct wl_resource* resource) { - struct sl_host_surface* host = wl_resource_get_user_data(resource); - struct sl_window *window, *surface_window = NULL; - struct sl_output_buffer* buffer; - - wl_list_for_each(window, &host->ctx->windows, link) { - if (window->host_surface_id == wl_resource_get_id(resource)) { - surface_window = window; - break; - } - } - - if (surface_window) { - surface_window->host_surface_id = 0; - sl_window_update(surface_window); - } - - if (host->contents_shm_mmap) - sl_mmap_unref(host->contents_shm_mmap); - - while (!wl_list_empty(&host->released_buffers)) { - buffer = wl_container_of(host->released_buffers.next, buffer, link); - sl_output_buffer_destroy(buffer); - } - while (!wl_list_empty(&host->busy_buffers)) { - buffer = wl_container_of(host->busy_buffers.next, buffer, link); - sl_output_buffer_destroy(buffer); - } - while (!wl_list_empty(&host->contents_viewport)) - wl_list_remove(host->contents_viewport.next); - - if (host->viewport) - wp_viewport_destroy(host->viewport); - wl_surface_destroy(host->proxy); - wl_resource_set_user_data(resource, NULL); - free(host); -} - -static void sl_surface_enter(void* data, - struct wl_surface* surface, - struct wl_output* output) { - struct sl_host_surface* host = wl_surface_get_user_data(surface); - struct sl_host_output* host_output = wl_output_get_user_data(output); - - wl_surface_send_enter(host->resource, host_output->resource); - host->has_output = 1; -} - -static void sl_surface_leave(void* data, - struct wl_surface* surface, - struct wl_output* output) { - struct sl_host_surface* host = wl_surface_get_user_data(surface); - struct sl_host_output* host_output = wl_output_get_user_data(output); - - wl_surface_send_leave(host->resource, host_output->resource); -} - -static const struct wl_surface_listener sl_surface_listener = { - sl_surface_enter, sl_surface_leave}; - -static void sl_region_destroy(struct wl_client* client, - struct wl_resource* resource) { - wl_resource_destroy(resource); -} - -static void sl_region_add(struct wl_client* client, - struct wl_resource* resource, - int32_t x, - int32_t y, - int32_t width, - int32_t height) { - struct sl_host_region* host = wl_resource_get_user_data(resource); - double scale = host->ctx->scale; - int32_t x1, y1, x2, y2; - - x1 = x / scale; - y1 = y / scale; - x2 = (x + width) / scale; - y2 = (y + height) / scale; - - wl_region_add(host->proxy, x1, y1, x2 - x1, y2 - y1); -} - -static void sl_region_subtract(struct wl_client* client, - struct wl_resource* resource, - int32_t x, - int32_t y, - int32_t width, - int32_t height) { - struct sl_host_region* host = wl_resource_get_user_data(resource); - double scale = host->ctx->scale; - int32_t x1, y1, x2, y2; - - x1 = x / scale; - y1 = y / scale; - x2 = (x + width) / scale; - y2 = (y + height) / scale; - - wl_region_subtract(host->proxy, x1, y1, x2 - x1, y2 - y1); -} - -static const struct wl_region_interface sl_region_implementation = { - sl_region_destroy, sl_region_add, sl_region_subtract}; - -static void sl_destroy_host_region(struct wl_resource* resource) { - struct sl_host_region* host = wl_resource_get_user_data(resource); - - wl_region_destroy(host->proxy); - wl_resource_set_user_data(resource, NULL); - free(host); -} - -static void sl_compositor_create_host_surface(struct wl_client* client, - struct wl_resource* resource, - uint32_t id) { - struct sl_host_compositor* host = wl_resource_get_user_data(resource); - struct sl_host_surface* host_surface; - struct sl_window *window, *unpaired_window = NULL; - - host_surface = malloc(sizeof(*host_surface)); - assert(host_surface); - - host_surface->ctx = host->compositor->ctx; - host_surface->contents_width = 0; - host_surface->contents_height = 0; - host_surface->contents_scale = 1; - wl_list_init(&host_surface->contents_viewport); - host_surface->contents_shm_mmap = NULL; - host_surface->has_role = 0; - host_surface->has_output = 0; - host_surface->last_event_serial = 0; - host_surface->current_buffer = NULL; - wl_list_init(&host_surface->released_buffers); - wl_list_init(&host_surface->busy_buffers); - host_surface->resource = wl_resource_create( - client, &wl_surface_interface, wl_resource_get_version(resource), id); - wl_resource_set_implementation(host_surface->resource, - &sl_surface_implementation, host_surface, - sl_destroy_host_surface); - host_surface->proxy = wl_compositor_create_surface(host->proxy); - wl_surface_set_user_data(host_surface->proxy, host_surface); - wl_surface_add_listener(host_surface->proxy, &sl_surface_listener, - host_surface); - host_surface->viewport = NULL; - if (host_surface->ctx->viewporter) { - host_surface->viewport = wp_viewporter_get_viewport( - host_surface->ctx->viewporter->internal, host_surface->proxy); - } - - wl_list_for_each(window, &host->compositor->ctx->unpaired_windows, link) { - if (window->host_surface_id == id) { - unpaired_window = window; - break; - } - } - - if (unpaired_window) - sl_window_update(window); -} - -static void sl_compositor_create_host_region(struct wl_client* client, - struct wl_resource* resource, - uint32_t id) { - struct sl_host_compositor* host = wl_resource_get_user_data(resource); - struct sl_host_region* host_region; - - host_region = malloc(sizeof(*host_region)); - assert(host_region); - - host_region->ctx = host->compositor->ctx; - host_region->resource = wl_resource_create( - client, &wl_region_interface, wl_resource_get_version(resource), id); - wl_resource_set_implementation(host_region->resource, - &sl_region_implementation, host_region, - sl_destroy_host_region); - host_region->proxy = wl_compositor_create_region(host->proxy); - wl_region_set_user_data(host_region->proxy, host_region); -} - -static const struct wl_compositor_interface sl_compositor_implementation = { - sl_compositor_create_host_surface, sl_compositor_create_host_region}; - -static void sl_destroy_host_compositor(struct wl_resource* resource) { - struct sl_host_compositor* host = wl_resource_get_user_data(resource); - - wl_compositor_destroy(host->proxy); - wl_resource_set_user_data(resource, NULL); - free(host); -} - -static void sl_bind_host_compositor(struct wl_client* client, - void* data, - uint32_t version, - uint32_t id) { - struct sl_context* ctx = (struct sl_context*)data; - struct sl_host_compositor* host; - - host = malloc(sizeof(*host)); - assert(host); - host->compositor = ctx->compositor; - host->resource = - wl_resource_create(client, &wl_compositor_interface, - MIN(version, ctx->compositor->version), id); - wl_resource_set_implementation(host->resource, &sl_compositor_implementation, - host, sl_destroy_host_compositor); - host->proxy = wl_registry_bind(wl_display_get_registry(ctx->display), - ctx->compositor->id, &wl_compositor_interface, - ctx->compositor->version); - wl_compositor_set_user_data(host->proxy, host); -} - -struct sl_global* sl_compositor_global_create(struct sl_context* ctx) { - return sl_global_create(ctx, &wl_compositor_interface, - ctx->compositor->version, ctx, - sl_bind_host_compositor); -} diff --git a/sommelier/sommelier-ctx.cc b/sommelier/sommelier-ctx.cc new file mode 100644 index 0000000..4ffabe7 --- /dev/null +++ b/sommelier/sommelier-ctx.cc @@ -0,0 +1,423 @@ +// Copyright 2021 The ChromiumOS Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sommelier-ctx.h" // NOLINT(build/include_directory) + +#include +#include +#include +#include +#include +#include + +#include "aura-shell-client-protocol.h" // NOLINT(build/include_directory) +#include "sommelier.h" // NOLINT(build/include_directory) +#include "sommelier-tracing.h" // NOLINT(build/include_directory) + +// TODO(b/173147612): Use container_token rather than this name. +#define DEFAULT_VM_NAME "termina" + +// Returns the string mapped to the given ATOM_ enum value. +// +// Note this is NOT the atom value sent via the X protocol, despite both being +// ints. Use |sl_context::atoms| to map between X protocol atoms and ATOM_ enum +// values: If `atoms[i].value = j`, i is the ATOM_ enum value and j is the +// X protocol atom. +// +// If the given value is out of range of the ATOM_ enum, returns NULL. +const char* sl_context_atom_name(int atom_enum) { + switch (atom_enum) { + case ATOM_WM_S0: + return "WM_S0"; + case ATOM_WM_PROTOCOLS: + return "WM_PROTOCOLS"; + case ATOM_WM_STATE: + return "WM_STATE"; + case ATOM_WM_CHANGE_STATE: + return "WM_CHANGE_STATE"; + case ATOM_WM_DELETE_WINDOW: + return "WM_DELETE_WINDOW"; + case ATOM_WM_TAKE_FOCUS: + return "WM_TAKE_FOCUS"; + case ATOM_WM_CLIENT_LEADER: + return "WM_CLIENT_LEADER"; + case ATOM_WL_SURFACE_ID: + return "WL_SURFACE_ID"; + case ATOM_UTF8_STRING: + return "UTF8_STRING"; + case ATOM_MOTIF_WM_HINTS: + return "_MOTIF_WM_HINTS"; + case ATOM_NET_ACTIVE_WINDOW: + return "_NET_ACTIVE_WINDOW"; + case ATOM_NET_FRAME_EXTENTS: + return "_NET_FRAME_EXTENTS"; + case ATOM_NET_STARTUP_ID: + return "_NET_STARTUP_ID"; + case ATOM_NET_SUPPORTED: + return "_NET_SUPPORTED"; + case ATOM_NET_SUPPORTING_WM_CHECK: + return "_NET_SUPPORTING_WM_CHECK"; + case ATOM_NET_WM_NAME: + return "_NET_WM_NAME"; + case ATOM_NET_WM_MOVERESIZE: + return "_NET_WM_MOVERESIZE"; + case ATOM_NET_WM_STATE: + return "_NET_WM_STATE"; + case ATOM_NET_WM_STATE_FULLSCREEN: + return "_NET_WM_STATE_FULLSCREEN"; + case ATOM_NET_WM_STATE_MAXIMIZED_VERT: + return "_NET_WM_STATE_MAXIMIZED_VERT"; + case ATOM_NET_WM_STATE_MAXIMIZED_HORZ: + return "_NET_WM_STATE_MAXIMIZED_HORZ"; + case ATOM_NET_WM_STATE_FOCUSED: + return "_NET_WM_STATE_FOCUSED"; + case ATOM_CLIPBOARD: + return "CLIPBOARD"; + case ATOM_CLIPBOARD_MANAGER: + return "CLIPBOARD_MANAGER"; + case ATOM_TARGETS: + return "TARGETS"; + case ATOM_TIMESTAMP: + return "TIMESTAMP"; + case ATOM_TEXT: + return "TEXT"; + case ATOM_INCR: + return "INCR"; + case ATOM_WL_SELECTION: + return "_WL_SELECTION"; + case ATOM_GTK_THEME_VARIANT: + return "_GTK_THEME_VARIANT"; + case ATOM_XWAYLAND_RANDR_EMU_MONITOR_RECTS: + return "_XWAYLAND_RANDR_EMU_MONITOR_RECTS"; + } + return NULL; +} + +void sl_context_init_default(struct sl_context* ctx) { + *ctx = {0}; + ctx->runprog = NULL; + ctx->display = NULL; + ctx->host_display = NULL; + ctx->client = NULL; + ctx->compositor = NULL; + ctx->subcompositor = NULL; + ctx->shm = NULL; + ctx->shell = NULL; + ctx->data_device_manager = NULL; + ctx->xdg_shell = NULL; + ctx->aura_shell = NULL; + ctx->viewporter = NULL; + ctx->linux_dmabuf = NULL; + ctx->keyboard_extension = NULL; + ctx->text_input_manager = NULL; + ctx->text_input_extension = NULL; + ctx->xdg_output_manager = NULL; +#ifdef GAMEPAD_SUPPORT + ctx->gaming_input_manager = NULL; +#endif + ctx->display_event_source = NULL; + ctx->display_ready_event_source = NULL; + ctx->sigchld_event_source = NULL; + ctx->sigusr1_event_source = NULL; + ctx->wm_fd = -1; + ctx->wayland_channel_fd = -1; + ctx->virtwl_socket_fd = -1; + ctx->virtwl_display_fd = -1; + ctx->wayland_channel_event_source = NULL; + ctx->virtwl_socket_event_source = NULL; + ctx->vm_id = DEFAULT_VM_NAME; + ctx->drm_device = NULL; + ctx->gbm = NULL; + ctx->xwayland = 0; + ctx->xwayland_pid = -1; + ctx->child_pid = -1; + ctx->peer_pid = -1; + ctx->xkb_context = NULL; + ctx->next_global_id = 1; + ctx->connection = NULL; + ctx->connection_event_source = NULL; + ctx->xfixes_extension = NULL; + ctx->screen = NULL; + ctx->window = 0; + ctx->host_focus_window = NULL; + ctx->needs_set_input_focus = 0; + ctx->desired_scale = 1.0; + ctx->scale = 1.0; + ctx->virt_scale_x = 1.0; + ctx->virt_scale_y = 1.0; + ctx->xdg_scale_x = 1.0; + ctx->xdg_scale_y = 1.0; + ctx->application_id = NULL; + ctx->application_id_property_name = NULL; + ctx->exit_with_child = 1; + ctx->sd_notify = NULL; + ctx->clipboard_manager = 0; + ctx->frame_color = 0xffffffff; + ctx->dark_frame_color = 0xff000000; + ctx->support_damage_buffer = true; + ctx->fullscreen_mode = ZAURA_SURFACE_FULLSCREEN_MODE_IMMERSIVE; + ctx->default_seat = NULL; + ctx->selection_window = XCB_WINDOW_NONE; + ctx->selection_owner = XCB_WINDOW_NONE; + ctx->selection_incremental_transfer = 0; + ctx->selection_request.requestor = XCB_NONE; + ctx->selection_request.property = XCB_ATOM_NONE; + ctx->selection_timestamp = XCB_CURRENT_TIME; + ctx->selection_data_device = NULL; + ctx->selection_data_offer = NULL; + ctx->selection_data_source = NULL; + ctx->selection_data_source_send_fd = -1; + ctx->selection_send_event_source = NULL; + ctx->selection_property_reply = NULL; + ctx->selection_property_offset = 0; + ctx->selection_event_source = NULL; + ctx->selection_data_offer_receive_fd = -1; + ctx->selection_data_ack_pending = 0; + for (unsigned i = 0; i < ARRAY_SIZE(ctx->atoms); i++) { + const char* name = sl_context_atom_name(i); + assert(name != NULL); + ctx->atoms[i].name = name; + } + ctx->timing = NULL; + ctx->trace_filename = NULL; + ctx->enable_xshape = false; + ctx->trace_system = false; + ctx->use_direct_scale = false; + + wl_list_init(&ctx->accelerators); + wl_list_init(&ctx->windowed_accelerators); + wl_list_init(&ctx->registries); + wl_list_init(&ctx->globals); + wl_list_init(&ctx->outputs); + wl_list_init(&ctx->seats); + wl_list_init(&ctx->windows); + wl_list_init(&ctx->unpaired_windows); + wl_list_init(&ctx->host_outputs); + wl_list_init(&ctx->selection_data_source_send_pending); +#ifdef GAMEPAD_SUPPORT + wl_list_init(&ctx->gamepads); +#endif +} + +static int sl_handle_clipboard_event(int fd, uint32_t mask, void* data) { + int rv; + struct sl_context* ctx = (struct sl_context*)data; + bool readable = false; + bool hang_up = false; + + if (mask & WL_EVENT_READABLE) + readable = true; + if (mask & WL_EVENT_HANGUP) + hang_up = true; + + rv = ctx->channel->handle_pipe(fd, readable, hang_up); + if (rv) { + fprintf(stderr, "reading pipe failed with %s\n", strerror(rv)); + return 0; + } + + if (hang_up) { + ctx->clipboard_event_source.reset(); + return 0; + } + + return 1; +} + +static int sl_handle_wayland_channel_event(int fd, uint32_t mask, void* data) { + TRACE_EVENT("surface", "sl_handle_wayland_channel_event"); + struct sl_context* ctx = (struct sl_context*)data; + struct WaylandSendReceive receive = {0}; + int pipe_read_fd = -1; + enum WaylandChannelEvent event_type = WaylandChannelEvent::None; + + char fd_buffer[CMSG_LEN(sizeof(int) * WAYLAND_MAX_FDs)]; + struct msghdr msg = {0}; + struct iovec buffer_iov; + ssize_t bytes; + int rv; + + if (!(mask & WL_EVENT_READABLE)) { + fprintf(stderr, + "Got error or hangup on virtwl ctx fd" + " (mask %d), exiting\n", + mask); + exit(EXIT_SUCCESS); + } + + receive.channel_fd = fd; + rv = ctx->channel->handle_channel_event(event_type, receive, pipe_read_fd); + if (rv) { + close(ctx->virtwl_socket_fd); + ctx->virtwl_socket_fd = -1; + return 0; + } + + if (event_type == WaylandChannelEvent::ReceiveAndProxy) { + struct wl_event_loop* event_loop = + wl_display_get_event_loop(ctx->host_display); + + ctx->clipboard_event_source.reset( + wl_event_loop_add_fd(event_loop, pipe_read_fd, WL_EVENT_READABLE, + sl_handle_clipboard_event, ctx)); + } else if (event_type != WaylandChannelEvent::Receive) { + return 1; + } + + buffer_iov.iov_base = receive.data; + buffer_iov.iov_len = receive.data_size; + + msg.msg_iov = &buffer_iov; + msg.msg_iovlen = 1; + msg.msg_control = fd_buffer; + + if (receive.num_fds) { + struct cmsghdr* cmsg; + + // Need to set msg_controllen so CMSG_FIRSTHDR will return the first + // cmsghdr. We copy every fd we just received from the ioctl into this + // cmsghdr. + msg.msg_controllen = sizeof(fd_buffer); + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(receive.num_fds * sizeof(int)); + memcpy(CMSG_DATA(cmsg), receive.fds, receive.num_fds * sizeof(int)); + msg.msg_controllen = cmsg->cmsg_len; + } + + bytes = sendmsg(ctx->virtwl_socket_fd, &msg, MSG_NOSIGNAL); + errno_assert(bytes == static_cast(receive.data_size)); + + while (receive.num_fds--) + close(receive.fds[receive.num_fds]); + + if (receive.data) + free(receive.data); + + return 1; +} + +static int sl_handle_virtwl_socket_event(int fd, uint32_t mask, void* data) { + TRACE_EVENT("surface", "sl_handle_virtwl_socket_event"); + struct sl_context* ctx = (struct sl_context*)data; + struct WaylandSendReceive send = {0}; + char fd_buffer[CMSG_LEN(sizeof(int) * WAYLAND_MAX_FDs)]; + uint8_t data_buffer[DEFAULT_BUFFER_SIZE]; + + struct iovec buffer_iov; + struct msghdr msg = {0}; + struct cmsghdr* cmsg; + ssize_t bytes; + int rv; + + if (!(mask & WL_EVENT_READABLE)) { + fprintf(stderr, + "Got error or hangup on virtwl socket" + " (mask %d), exiting\n", + mask); + exit(EXIT_SUCCESS); + } + + buffer_iov.iov_base = data_buffer; + buffer_iov.iov_len = ctx->channel->max_send_size(); + + msg.msg_iov = &buffer_iov; + msg.msg_iovlen = 1; + msg.msg_control = fd_buffer; + msg.msg_controllen = sizeof(fd_buffer); + + bytes = recvmsg(ctx->virtwl_socket_fd, &msg, 0); + errno_assert(bytes > 0); + + // If there were any FDs recv'd by recvmsg, there will be some data in the + // msg_control buffer. To get the FDs out we iterate all cmsghdr's within and + // unpack the FDs if the cmsghdr type is SCM_RIGHTS. + for (cmsg = msg.msg_controllen != 0 ? CMSG_FIRSTHDR(&msg) : NULL; cmsg; + cmsg = CMSG_NXTHDR(&msg, cmsg)) { + size_t cmsg_fd_count; + + if (cmsg->cmsg_level != SOL_SOCKET || cmsg->cmsg_type != SCM_RIGHTS) + continue; + + cmsg_fd_count = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int); + + // fd_count will never exceed WAYLAND_MAX_FDs because the + // control message buffer only allocates enough space for that many FDs. + memcpy(&send.fds[send.num_fds], CMSG_DATA(cmsg), + cmsg_fd_count * sizeof(int)); + send.num_fds += cmsg_fd_count; + } + + send.channel_fd = ctx->wayland_channel_fd; + send.data = data_buffer; + send.data_size = bytes; + + rv = ctx->channel->send(send); + errno_assert(!rv); + + while (send.num_fds--) + close(send.fds[send.num_fds]); + + return 1; +} + +bool sl_context_init_wayland_channel(struct sl_context* ctx, + struct wl_event_loop* event_loop, + bool display) { + if (ctx->channel == NULL) { + // Running in noop mode, without virtualization. + return true; + } + int rv = ctx->channel->init(); + if (rv) { + fprintf(stderr, "error: could not initialize wayland channel: %s\n", + strerror(-rv)); + return false; + } + if (!display) { + // We use a wayland virtual context unless display was explicitly specified. + // WARNING: It's critical that we never call wl_display_roundtrip + // as we're not spawning a new thread to handle forwarding. Calling + // wl_display_roundtrip will cause a deadlock. + int vws[2]; + + // Connection to virtwl channel. + rv = socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, vws); + errno_assert(!rv); + + ctx->virtwl_socket_fd = vws[0]; + ctx->virtwl_display_fd = vws[1]; + + rv = ctx->channel->create_context(ctx->wayland_channel_fd); + if (rv) { + fprintf(stderr, "error: failed to create virtwl context: %s\n", + strerror(-rv)); + return false; + } + + ctx->virtwl_socket_event_source.reset(wl_event_loop_add_fd( + event_loop, ctx->virtwl_socket_fd, WL_EVENT_READABLE, + sl_handle_virtwl_socket_event, ctx)); + ctx->wayland_channel_event_source.reset(wl_event_loop_add_fd( + event_loop, ctx->wayland_channel_fd, WL_EVENT_READABLE, + sl_handle_wayland_channel_event, ctx)); + } + return true; +} + +sl_window* sl_context_lookup_window_for_surface(struct sl_context* ctx, + wl_resource* resource) { + sl_window* surface_window = NULL; + sl_window* window; + + wl_list_for_each(window, &ctx->windows, link) { + if (window->host_surface_id == wl_resource_get_id(resource)) { + surface_window = window; + break; + } + } + + return surface_window; +} diff --git a/sommelier/sommelier-ctx.h b/sommelier/sommelier-ctx.h new file mode 100644 index 0000000..4af0e7c --- /dev/null +++ b/sommelier/sommelier-ctx.h @@ -0,0 +1,215 @@ +// Copyright 2021 The ChromiumOS Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef VM_TOOLS_SOMMELIER_SOMMELIER_CTX_H_ +#define VM_TOOLS_SOMMELIER_SOMMELIER_CTX_H_ + +#include +#include +#include +#include +#include +#include + +#include "sommelier-timing.h" // NOLINT(build/include_directory) +#include "sommelier-util.h" // NOLINT(build/include_directory) +#include "virtualization/wayland_channel.h" + +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) + +#define MIN_SIZE (INT_MIN / 10) +#define MAX_SIZE (INT_MAX / 10) + +// A list of atoms to intern (create/fetch) when connecting to the X server. +// +// To add an atom, declare it here and define it in |sl_context_atom_name|. +enum { + ATOM_WM_S0, + ATOM_WM_PROTOCOLS, + ATOM_WM_STATE, + ATOM_WM_CHANGE_STATE, + ATOM_WM_DELETE_WINDOW, + ATOM_WM_TAKE_FOCUS, + ATOM_WM_CLIENT_LEADER, + ATOM_WL_SURFACE_ID, + ATOM_UTF8_STRING, + ATOM_MOTIF_WM_HINTS, + ATOM_NET_ACTIVE_WINDOW, + ATOM_NET_FRAME_EXTENTS, + ATOM_NET_STARTUP_ID, + ATOM_NET_SUPPORTED, + ATOM_NET_SUPPORTING_WM_CHECK, + ATOM_NET_WM_NAME, + ATOM_NET_WM_MOVERESIZE, + ATOM_NET_WM_STATE, + ATOM_NET_WM_STATE_FULLSCREEN, + ATOM_NET_WM_STATE_MAXIMIZED_VERT, + ATOM_NET_WM_STATE_MAXIMIZED_HORZ, + ATOM_NET_WM_STATE_FOCUSED, + ATOM_CLIPBOARD, + ATOM_CLIPBOARD_MANAGER, + ATOM_TARGETS, + ATOM_TIMESTAMP, + ATOM_TEXT, + ATOM_INCR, + ATOM_WL_SELECTION, + ATOM_GTK_THEME_VARIANT, + ATOM_XWAYLAND_RANDR_EMU_MONITOR_RECTS, + ATOM_LAST = ATOM_XWAYLAND_RANDR_EMU_MONITOR_RECTS, +}; + +struct sl_context { + char** runprog; + struct wl_display* display; + struct wl_display* host_display; + struct wl_client* client; + struct sl_compositor* compositor; + struct sl_subcompositor* subcompositor; + struct sl_shm* shm; + struct sl_shell* shell; + struct sl_data_device_manager* data_device_manager; + struct sl_xdg_shell* xdg_shell; + struct sl_aura_shell* aura_shell; + struct sl_viewporter* viewporter; + struct sl_linux_dmabuf* linux_dmabuf; + struct sl_linux_explicit_synchronization* linux_explicit_synchronization; + struct sl_keyboard_extension* keyboard_extension; + struct sl_text_input_manager* text_input_manager; + struct sl_text_input_extension* text_input_extension; + struct sl_xdg_output_manager* xdg_output_manager; +#ifdef GAMEPAD_SUPPORT + struct sl_gaming_input_manager* gaming_input_manager; +#endif + struct sl_relative_pointer_manager* relative_pointer_manager; + struct sl_pointer_constraints* pointer_constraints; + struct wl_list outputs; + struct wl_list seats; + std::unique_ptr display_event_source; + std::unique_ptr display_ready_event_source; + std::unique_ptr sigchld_event_source; + std::unique_ptr sigusr1_event_source; + std::unique_ptr clipboard_event_source; + struct wl_array dpi; + int wm_fd; + int wayland_channel_fd; + int virtwl_socket_fd; + int virtwl_display_fd; + std::unique_ptr wayland_channel_event_source; + std::unique_ptr virtwl_socket_event_source; + const char* drm_device; + struct gbm_device* gbm; + int xwayland; + pid_t xwayland_pid; + // XWayland-hosting sommelier instances allow additional connections for IME + // support. + wl_listener extra_client_created_listener; + pid_t child_pid; + pid_t peer_pid; + struct xkb_context* xkb_context; + struct wl_list accelerators; + struct wl_list windowed_accelerators; + struct wl_list registries; + struct wl_list globals; + struct wl_list host_outputs; + int next_global_id; + xcb_connection_t* connection; + std::unique_ptr connection_event_source; + const xcb_query_extension_reply_t* xfixes_extension; + const xcb_query_extension_reply_t* xshape_extension; + xcb_screen_t* screen; + xcb_window_t window; + struct wl_list windows, unpaired_windows; + struct sl_window* host_focus_window; + int needs_set_input_focus; +#ifdef GAMEPAD_SUPPORT + struct wl_list gamepads; +#endif + double desired_scale; + double scale; + + // These scale factors are used for the direct scaling mode. + // These factors are set to the values computed from the internal/default + // display. + // See sommelier-transform.h and the definition in sl_output + // for more details on this. + double virt_scale_x, virt_scale_y; + double xdg_scale_x, xdg_scale_y; + + // If non-null, all X11 client apps will be given this application ID. + const char* application_id; + + // VM name embedded in application IDs, if application_id is null. + const char* vm_id; + + // A custom X11 property to append to application IDs, if application_id + // is null. Used in preference to other X11 properties such as WM_CLASS. + const char* application_id_property_name; + xcb_atom_t application_id_property_atom = XCB_ATOM_NONE; + + int exit_with_child; + const char* sd_notify; + int clipboard_manager; + uint32_t frame_color; + uint32_t dark_frame_color; + bool support_damage_buffer; + int fullscreen_mode; + struct sl_host_seat* default_seat; + xcb_window_t selection_window; + xcb_window_t selection_owner; + int selection_incremental_transfer; + xcb_selection_request_event_t selection_request; + xcb_timestamp_t selection_timestamp; + struct wl_data_device* selection_data_device; + struct sl_data_offer* selection_data_offer; + struct sl_data_source* selection_data_source; + int selection_data_source_send_fd; + struct wl_list selection_data_source_send_pending; + std::unique_ptr selection_send_event_source; + xcb_get_property_reply_t* selection_property_reply; + int selection_property_offset; + std::unique_ptr selection_event_source; + xcb_atom_t selection_data_type; + struct wl_array selection_data; + int selection_data_offer_receive_fd; + int selection_data_ack_pending; + union { + const char* name; + xcb_intern_atom_cookie_t cookie; + xcb_atom_t value; + } atoms[ATOM_LAST + 1]; + xcb_visualid_t visual_ids[256]; + xcb_colormap_t colormaps[256]; + Timing* timing; + const char* trace_filename; + bool enable_xshape; + bool trace_system; + bool use_explicit_fence; + bool use_virtgpu_channel; + bool use_direct_scale; + // Never freed after allocation due the fact sommelier doesn't have a + // shutdown function yet. + WaylandChannel* channel; +}; + +// Returns the string mapped to the given ATOM_ enum value. +// +// Note this is NOT the atom value sent via the X protocol, despite both being +// ints. Use |sl_context::atoms| to map between X protocol atoms and ATOM_ enum +// values: If `atoms[i].value = j`, i is the ATOM_ enum value and j is the +// X protocol atom. +// +// If the given value is out of range of the ATOM_ enum, returns NULL. +const char* sl_context_atom_name(int atom_enum); + +void sl_context_init_default(struct sl_context* ctx); + +bool sl_context_init_wayland_channel(struct sl_context* ctx, + struct wl_event_loop* event_loop, + bool display); + +sl_window* sl_context_lookup_window_for_surface(struct sl_context* ctx, + wl_resource* resource); + +#endif // VM_TOOLS_SOMMELIER_SOMMELIER_CTX_H_ diff --git a/sommelier/sommelier-data-device-manager.c b/sommelier/sommelier-data-device-manager.cc similarity index 65% rename from sommelier/sommelier-data-device-manager.c rename to sommelier/sommelier-data-device-manager.cc index 4693735..004b93e 100644 --- a/sommelier/sommelier-data-device-manager.c +++ b/sommelier/sommelier-data-device-manager.cc @@ -1,13 +1,13 @@ -// Copyright 2018 The Chromium OS Authors. All rights reserved. +// Copyright 2018 The ChromiumOS Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "sommelier.h" +#include "sommelier.h" // NOLINT(build/include_directory) +#include "sommelier-transform.h" // NOLINT(build/include_directory) #include #include #include -#include #include #include #include @@ -25,37 +25,45 @@ struct sl_host_data_device { struct sl_context* ctx; struct wl_resource* resource; struct wl_data_device* proxy; + struct sl_host_surface* focus_surface; }; +MAP_STRUCTS(wl_data_device, sl_host_data_device); struct sl_host_data_source { struct wl_resource* resource; struct wl_data_source* proxy; }; +MAP_STRUCTS(wl_data_source, sl_host_data_source); struct sl_host_data_offer { struct sl_context* ctx; struct wl_resource* resource; struct wl_data_offer* proxy; }; +MAP_STRUCTS(wl_data_offer, sl_host_data_offer); struct sl_data_transfer { int read_fd; int write_fd; size_t offset; size_t bytes_left; - uint8_t data[4096]; - struct wl_event_source* read_event_source; - struct wl_event_source* write_event_source; + uint8_t data[DEFAULT_BUFFER_SIZE]; + std::unique_ptr read_event_source; + std::unique_ptr write_event_source; }; static void sl_data_transfer_destroy(struct sl_data_transfer* transfer) { - assert(transfer->read_event_source); - wl_event_source_remove(transfer->read_event_source); - assert(transfer->write_event_source); - wl_event_source_remove(transfer->write_event_source); - close(transfer->read_fd); - close(transfer->write_fd); - free(transfer); + int read_fd = transfer->read_fd; + int write_fd = transfer->write_fd; + + // default deleter of wl_event_source removes the sources from their + // associated wl_event_loop, but we still want to close the fd. Ordered this + // way because we don't want any additional events associated with the close() + // calls to end up a the wl_event_loop. + delete transfer; + + close(read_fd); + close(write_fd); } static int sl_handle_data_transfer_read(int fd, uint32_t mask, void* data) { @@ -88,8 +96,9 @@ static int sl_handle_data_transfer_read(int fd, uint32_t mask, void* data) { transfer->offset = 0; // There may still be data to read from the event source, but we have no // room in our buffer so move to the writing state. - wl_event_source_fd_update(transfer->read_event_source, 0); - wl_event_source_fd_update(transfer->write_event_source, WL_EVENT_WRITABLE); + wl_event_source_fd_update(transfer->read_event_source.get(), 0); + wl_event_source_fd_update(transfer->write_event_source.get(), + WL_EVENT_WRITABLE); } else { // On a read error or EOF, end the transfer. sl_data_transfer_destroy(transfer); @@ -121,15 +130,16 @@ static int sl_handle_data_transfer_write(int fd, uint32_t mask, void* data) { // On a write error, end the transfer. sl_data_transfer_destroy(transfer); } else { - assert(rv <= transfer->bytes_left); + assert(rv <= static_cast(transfer->bytes_left)); transfer->bytes_left -= rv; transfer->offset += rv; } if (!transfer->bytes_left) { // If all data has been written, move back to the reading state. - wl_event_source_fd_update(transfer->write_event_source, 0); - wl_event_source_fd_update(transfer->read_event_source, WL_EVENT_READABLE); + wl_event_source_fd_update(transfer->write_event_source.get(), 0); + wl_event_source_fd_update(transfer->read_event_source.get(), + WL_EVENT_READABLE); } // If there is still data left, continue in the writing state. @@ -139,7 +149,6 @@ static int sl_handle_data_transfer_write(int fd, uint32_t mask, void* data) { static void sl_data_transfer_create(struct wl_event_loop* event_loop, int read_fd, int write_fd) { - struct sl_data_transfer* transfer; int flags; int rv; @@ -149,62 +158,46 @@ static void sl_data_transfer_create(struct wl_event_loop* event_loop, UNUSED(rv); // Start out the transfer in the reading state. - transfer = malloc(sizeof(*transfer)); + struct sl_data_transfer* transfer = new sl_data_transfer; assert(transfer); transfer->read_fd = read_fd; transfer->write_fd = write_fd; transfer->offset = 0; transfer->bytes_left = 0; - transfer->read_event_source = + memset(transfer->data, 0, DEFAULT_BUFFER_SIZE); + transfer->read_event_source.reset( wl_event_loop_add_fd(event_loop, read_fd, WL_EVENT_READABLE, - sl_handle_data_transfer_read, transfer); - transfer->write_event_source = wl_event_loop_add_fd( - event_loop, write_fd, 0, sl_handle_data_transfer_write, transfer); -} - -static void sl_data_offer_accept(struct wl_client* client, - struct wl_resource* resource, - uint32_t serial, - const char* mime_type) { - struct sl_host_data_offer* host = wl_resource_get_user_data(resource); - - wl_data_offer_accept(host->proxy, serial, mime_type); + sl_handle_data_transfer_read, transfer)); + transfer->write_event_source.reset(wl_event_loop_add_fd( + event_loop, write_fd, 0, sl_handle_data_transfer_write, transfer)); } static void sl_data_offer_receive(struct wl_client* client, struct wl_resource* resource, const char* mime_type, int32_t fd) { - struct sl_host_data_offer* host = wl_resource_get_user_data(resource); + struct sl_host_data_offer* host = + static_cast(wl_resource_get_user_data(resource)); - switch (host->ctx->data_driver) { - case DATA_DRIVER_VIRTWL: { - struct virtwl_ioctl_new new_pipe = { - .type = VIRTWL_IOCTL_NEW_PIPE_READ, - .fd = -1, - .flags = 0, - .size = 0, - }; - int rv; - - rv = ioctl(host->ctx->virtwl_fd, VIRTWL_IOCTL_NEW, &new_pipe); - if (rv) { - fprintf(stderr, "error: failed to create virtwl pipe: %s\n", - strerror(errno)); - close(fd); - return; - } - - sl_data_transfer_create( - wl_display_get_event_loop(host->ctx->host_display), new_pipe.fd, fd); - - wl_data_offer_receive(host->proxy, mime_type, new_pipe.fd); - } break; - case DATA_DRIVER_NOOP: - wl_data_offer_receive(host->proxy, mime_type, fd); - close(fd); - break; + if (host->ctx->channel == NULL) { + // Running in noop mode, without virtualization. + wl_data_offer_receive(host->proxy, mime_type, fd); + close(fd); + return; } + + int pipe_fd, rv; + rv = host->ctx->channel->create_pipe(pipe_fd); + if (rv) { + fprintf(stderr, "error: failed to create virtwl pipe: %s\n", strerror(-rv)); + close(fd); + return; + } + + sl_data_transfer_create(wl_display_get_event_loop(host->ctx->host_display), + pipe_fd, fd); + + wl_data_offer_receive(host->proxy, mime_type, pipe_fd); } static void sl_data_offer_destroy(struct wl_client* client, @@ -212,30 +205,16 @@ static void sl_data_offer_destroy(struct wl_client* client, wl_resource_destroy(resource); } -static void sl_data_offer_finish(struct wl_client* client, - struct wl_resource* resource) { - struct sl_host_data_offer* host = wl_resource_get_user_data(resource); - - wl_data_offer_finish(host->proxy); -} - -static void sl_data_offer_set_actions(struct wl_client* client, - struct wl_resource* resource, - uint32_t dnd_actions, - uint32_t preferred_action) { - struct sl_host_data_offer* host = wl_resource_get_user_data(resource); - - wl_data_offer_set_actions(host->proxy, dnd_actions, preferred_action); -} - static const struct wl_data_offer_interface sl_data_offer_implementation = { - sl_data_offer_accept, sl_data_offer_receive, sl_data_offer_destroy, - sl_data_offer_finish, sl_data_offer_set_actions}; + ForwardRequest, sl_data_offer_receive, + sl_data_offer_destroy, ForwardRequest, + ForwardRequest}; static void sl_data_offer_offer(void* data, struct wl_data_offer* data_offer, const char* mime_type) { - struct sl_host_data_offer* host = wl_data_offer_get_user_data(data_offer); + struct sl_host_data_offer* host = + static_cast(wl_data_offer_get_user_data(data_offer)); wl_data_offer_send_offer(host->resource, mime_type); } @@ -243,7 +222,8 @@ static void sl_data_offer_offer(void* data, static void sl_data_offer_source_actions(void* data, struct wl_data_offer* data_offer, uint32_t source_actions) { - struct sl_host_data_offer* host = wl_data_offer_get_user_data(data_offer); + struct sl_host_data_offer* host = + static_cast(wl_data_offer_get_user_data(data_offer)); wl_data_offer_send_source_actions(host->resource, source_actions); } @@ -251,7 +231,8 @@ static void sl_data_offer_source_actions(void* data, static void sl_data_offer_action(void* data, struct wl_data_offer* data_offer, uint32_t dnd_action) { - struct sl_host_data_offer* host = wl_data_offer_get_user_data(data_offer); + struct sl_host_data_offer* host = + static_cast(wl_data_offer_get_user_data(data_offer)); wl_data_offer_send_action(host->resource, dnd_action); } @@ -260,19 +241,12 @@ static const struct wl_data_offer_listener sl_data_offer_listener = { sl_data_offer_offer, sl_data_offer_source_actions, sl_data_offer_action}; static void sl_destroy_host_data_offer(struct wl_resource* resource) { - struct sl_host_data_offer* host = wl_resource_get_user_data(resource); + struct sl_host_data_offer* host = + static_cast(wl_resource_get_user_data(resource)); wl_data_offer_destroy(host->proxy); wl_resource_set_user_data(resource, NULL); - free(host); -} - -static void sl_data_source_offer(struct wl_client* client, - struct wl_resource* resource, - const char* mime_type) { - struct sl_host_data_source* host = wl_resource_get_user_data(resource); - - wl_data_source_offer(host->proxy, mime_type); + delete host; } static void sl_data_source_destroy(struct wl_client* client, @@ -280,21 +254,15 @@ static void sl_data_source_destroy(struct wl_client* client, wl_resource_destroy(resource); } -static void sl_data_source_set_actions(struct wl_client* client, - struct wl_resource* resource, - uint32_t dnd_actions) { - struct sl_host_data_source* host = wl_resource_get_user_data(resource); - - wl_data_source_set_actions(host->proxy, dnd_actions); -} - static const struct wl_data_source_interface sl_data_source_implementation = { - sl_data_source_offer, sl_data_source_destroy, sl_data_source_set_actions}; + ForwardRequest, sl_data_source_destroy, + ForwardRequest}; static void sl_data_source_target(void* data, struct wl_data_source* data_source, const char* mime_type) { - struct sl_host_data_source* host = wl_data_source_get_user_data(data_source); + struct sl_host_data_source* host = static_cast( + wl_data_source_get_user_data(data_source)); wl_data_source_send_target(host->resource, mime_type); } @@ -303,7 +271,8 @@ static void sl_data_source_send(void* data, struct wl_data_source* data_source, const char* mime_type, int32_t fd) { - struct sl_host_data_source* host = wl_data_source_get_user_data(data_source); + struct sl_host_data_source* host = static_cast( + wl_data_source_get_user_data(data_source)); wl_data_source_send_send(host->resource, mime_type, fd); close(fd); @@ -311,21 +280,24 @@ static void sl_data_source_send(void* data, static void sl_data_source_cancelled(void* data, struct wl_data_source* data_source) { - struct sl_host_data_source* host = wl_data_source_get_user_data(data_source); + struct sl_host_data_source* host = static_cast( + wl_data_source_get_user_data(data_source)); wl_data_source_send_cancelled(host->resource); } void sl_data_source_dnd_drop_performed(void* data, struct wl_data_source* data_source) { - struct sl_host_data_source* host = wl_data_source_get_user_data(data_source); + struct sl_host_data_source* host = static_cast( + wl_data_source_get_user_data(data_source)); wl_data_source_send_dnd_drop_performed(host->resource); } void sl_data_source_dnd_finished(void* data, struct wl_data_source* data_source) { - struct sl_host_data_source* host = wl_data_source_get_user_data(data_source); + struct sl_host_data_source* host = static_cast( + wl_data_source_get_user_data(data_source)); wl_data_source_send_dnd_finished(host->resource); } @@ -333,7 +305,8 @@ void sl_data_source_dnd_finished(void* data, void sl_data_source_actions(void* data, struct wl_data_source* data_source, uint32_t dnd_action) { - struct sl_host_data_source* host = wl_data_source_get_user_data(data_source); + struct sl_host_data_source* host = static_cast( + wl_data_source_get_user_data(data_source)); wl_data_source_send_action(host->resource, dnd_action); } @@ -344,11 +317,12 @@ static const struct wl_data_source_listener sl_data_source_listener = { sl_data_source_dnd_finished, sl_data_source_actions}; static void sl_destroy_host_data_source(struct wl_resource* resource) { - struct sl_host_data_source* host = wl_resource_get_user_data(resource); + struct sl_host_data_source* host = + static_cast(wl_resource_get_user_data(resource)); wl_data_source_destroy(host->proxy); wl_resource_set_user_data(resource, NULL); - free(host); + delete host; } static void sl_data_device_start_drag(struct wl_client* client, @@ -357,50 +331,44 @@ static void sl_data_device_start_drag(struct wl_client* client, struct wl_resource* origin_resource, struct wl_resource* icon_resource, uint32_t serial) { - struct sl_host_data_device* host = wl_resource_get_user_data(resource); + struct sl_host_data_device* host = + static_cast(wl_resource_get_user_data(resource)); struct sl_host_data_source* host_source = - source_resource ? wl_resource_get_user_data(source_resource) : NULL; + source_resource ? static_cast( + wl_resource_get_user_data(source_resource)) + : NULL; struct sl_host_surface* host_origin = - origin_resource ? wl_resource_get_user_data(origin_resource) : NULL; + origin_resource ? static_cast( + wl_resource_get_user_data(origin_resource)) + : NULL; struct sl_host_surface* host_icon = - icon_resource ? wl_resource_get_user_data(icon_resource) : NULL; + icon_resource ? static_cast( + wl_resource_get_user_data(icon_resource)) + : NULL; host_icon->has_role = 1; wl_data_device_start_drag(host->proxy, host_source ? host_source->proxy : NULL, host_origin ? host_origin->proxy : NULL, host_icon ? host_icon->proxy : NULL, serial); -} - -static void sl_data_device_set_selection(struct wl_client* client, - struct wl_resource* resource, - struct wl_resource* source_resource, - uint32_t serial) { - struct sl_host_data_device* host = wl_resource_get_user_data(resource); - struct sl_host_data_source* host_source = - source_resource ? wl_resource_get_user_data(source_resource) : NULL; - - wl_data_device_set_selection(host->proxy, - host_source ? host_source->proxy : NULL, serial); -} +} // NOLINT(whitespace/indent) static void sl_data_device_release(struct wl_client* client, struct wl_resource* resource) { wl_resource_destroy(resource); -} +} // NOLINT(whitespace/indent) static const struct wl_data_device_interface sl_data_device_implementation = { - sl_data_device_start_drag, sl_data_device_set_selection, + sl_data_device_start_drag, + ForwardRequest, sl_data_device_release}; static void sl_data_device_data_offer(void* data, struct wl_data_device* data_device, struct wl_data_offer* data_offer) { - struct sl_host_data_device* host = wl_data_device_get_user_data(data_device); - struct sl_host_data_offer* host_data_offer; - - host_data_offer = malloc(sizeof(*host_data_offer)); - assert(host_data_offer); + struct sl_host_data_device* host = static_cast( + wl_data_device_get_user_data(data_device)); + struct sl_host_data_offer* host_data_offer = new sl_host_data_offer(); host_data_offer->ctx = host->ctx; host_data_offer->resource = wl_resource_create( @@ -410,7 +378,6 @@ static void sl_data_device_data_offer(void* data, &sl_data_offer_implementation, host_data_offer, sl_destroy_host_data_offer); host_data_offer->proxy = data_offer; - wl_data_offer_set_user_data(host_data_offer->proxy, host_data_offer); wl_data_offer_add_listener(host_data_offer->proxy, &sl_data_offer_listener, host_data_offer); @@ -424,22 +391,28 @@ static void sl_data_device_enter(void* data, wl_fixed_t x, wl_fixed_t y, struct wl_data_offer* data_offer) { - struct sl_host_data_device* host = wl_data_device_get_user_data(data_device); - struct sl_host_surface* host_surface = wl_surface_get_user_data(surface); + struct sl_host_data_device* host = static_cast( + wl_data_device_get_user_data(data_device)); + struct sl_host_surface* host_surface = + static_cast(wl_surface_get_user_data(surface)); struct sl_host_data_offer* host_data_offer = - wl_data_offer_get_user_data(data_offer); - double scale = host->ctx->scale; + static_cast(wl_data_offer_get_user_data(data_offer)); + wl_fixed_t ix = x, iy = y; - wl_data_device_send_enter(host->resource, serial, host_surface->resource, - wl_fixed_from_double(wl_fixed_to_double(x) * scale), - wl_fixed_from_double(wl_fixed_to_double(y) * scale), - host_data_offer->resource); -} + sl_transform_host_to_guest_fixed(host->ctx, host_surface, &ix, &iy); + + host->focus_surface = host_surface; + + wl_data_device_send_enter(host->resource, serial, host_surface->resource, ix, + iy, host_data_offer->resource); +} // NOLINT(whitespace/indent) static void sl_data_device_leave(void* data, struct wl_data_device* data_device) { - struct sl_host_data_device* host = wl_data_device_get_user_data(data_device); + struct sl_host_data_device* host = static_cast( + wl_data_device_get_user_data(data_device)); + host->focus_surface = NULL; wl_data_device_send_leave(host->resource); } @@ -448,29 +421,36 @@ static void sl_data_device_motion(void* data, uint32_t time, wl_fixed_t x, wl_fixed_t y) { - struct sl_host_data_device* host = wl_data_device_get_user_data(data_device); - double scale = host->ctx->scale; + struct sl_host_data_device* host = static_cast( + wl_data_device_get_user_data(data_device)); + wl_fixed_t ix = x, iy = y; - wl_data_device_send_motion( - host->resource, time, wl_fixed_from_double(wl_fixed_to_double(x) * scale), - wl_fixed_from_double(wl_fixed_to_double(y) * scale)); + sl_transform_host_to_guest_fixed(host->ctx, host->focus_surface, &ix, &iy); + + wl_data_device_send_motion(host->resource, time, ix, iy); } static void sl_data_device_drop(void* data, struct wl_data_device* data_device) { - struct sl_host_data_device* host = wl_data_device_get_user_data(data_device); + struct sl_host_data_device* host = static_cast( + wl_data_device_get_user_data(data_device)); + host->focus_surface = NULL; wl_data_device_send_drop(host->resource); } static void sl_data_device_selection(void* data, struct wl_data_device* data_device, struct wl_data_offer* data_offer) { - struct sl_host_data_device* host = wl_data_device_get_user_data(data_device); - struct sl_host_data_offer* host_data_offer = - wl_data_offer_get_user_data(data_offer); + struct sl_host_data_device* host = static_cast( + wl_data_device_get_user_data(data_device)); + struct wl_resource* data_offer_resource = + data_offer ? static_cast( + wl_data_offer_get_user_data(data_offer)) + ->resource + : nullptr; - wl_data_device_send_selection(host->resource, host_data_offer->resource); + wl_data_device_send_selection(host->resource, data_offer_resource); } static const struct wl_data_device_listener sl_data_device_listener = { @@ -478,7 +458,8 @@ static const struct wl_data_device_listener sl_data_device_listener = { sl_data_device_motion, sl_data_device_drop, sl_data_device_selection}; static void sl_destroy_host_data_device(struct wl_resource* resource) { - struct sl_host_data_device* host = wl_resource_get_user_data(resource); + struct sl_host_data_device* host = + static_cast(wl_resource_get_user_data(resource)); if (wl_data_device_get_version(host->proxy) >= WL_DATA_DEVICE_RELEASE_SINCE_VERSION) { @@ -487,17 +468,15 @@ static void sl_destroy_host_data_device(struct wl_resource* resource) { wl_data_device_destroy(host->proxy); } wl_resource_set_user_data(resource, NULL); - free(host); + delete host; } static void sl_data_device_manager_create_data_source( struct wl_client* client, struct wl_resource* resource, uint32_t id) { struct sl_host_data_device_manager* host = - wl_resource_get_user_data(resource); - struct sl_host_data_source* host_data_source; - - host_data_source = malloc(sizeof(*host_data_source)); - assert(host_data_source); + static_cast( + wl_resource_get_user_data(resource)); + struct sl_host_data_source* host_data_source = new sl_host_data_source(); host_data_source->resource = wl_resource_create( client, &wl_data_source_interface, wl_resource_get_version(resource), id); @@ -506,7 +485,6 @@ static void sl_data_device_manager_create_data_source( host_data_source, sl_destroy_host_data_source); host_data_source->proxy = wl_data_device_manager_create_data_source(host->proxy); - wl_data_source_set_user_data(host_data_source->proxy, host_data_source); wl_data_source_add_listener(host_data_source->proxy, &sl_data_source_listener, host_data_source); } @@ -517,14 +495,14 @@ static void sl_data_device_manager_get_data_device( uint32_t id, struct wl_resource* seat_resource) { struct sl_host_data_device_manager* host = - wl_resource_get_user_data(resource); - struct sl_host_seat* host_seat = wl_resource_get_user_data(seat_resource); - struct sl_host_data_device* host_data_device; - - host_data_device = malloc(sizeof(*host_data_device)); - assert(host_data_device); + static_cast( + wl_resource_get_user_data(resource)); + struct sl_host_seat* host_seat = + static_cast(wl_resource_get_user_data(seat_resource)); + struct sl_host_data_device* host_data_device = new sl_host_data_device(); host_data_device->ctx = host->ctx; + host_data_device->focus_surface = NULL; host_data_device->resource = wl_resource_create( client, &wl_data_device_interface, wl_resource_get_version(resource), id); wl_resource_set_implementation(host_data_device->resource, @@ -532,10 +510,9 @@ static void sl_data_device_manager_get_data_device( host_data_device, sl_destroy_host_data_device); host_data_device->proxy = wl_data_device_manager_get_data_device(host->proxy, host_seat->proxy); - wl_data_device_set_user_data(host_data_device->proxy, host_data_device); wl_data_device_add_listener(host_data_device->proxy, &sl_data_device_listener, host_data_device); -} +} // NOLINT(whitespace/indent) static const struct wl_data_device_manager_interface sl_data_device_manager_implementation = { @@ -544,11 +521,12 @@ static const struct wl_data_device_manager_interface static void sl_destroy_host_data_device_manager(struct wl_resource* resource) { struct sl_host_data_device_manager* host = - wl_resource_get_user_data(resource); + static_cast( + wl_resource_get_user_data(resource)); wl_data_device_manager_destroy(host->proxy); wl_resource_set_user_data(resource, NULL); - free(host); + delete host; } static void sl_bind_host_data_device_manager(struct wl_client* client, @@ -556,10 +534,7 @@ static void sl_bind_host_data_device_manager(struct wl_client* client, uint32_t version, uint32_t id) { struct sl_context* ctx = (struct sl_context*)data; - struct sl_host_data_device_manager* host; - - host = malloc(sizeof(*host)); - assert(host); + struct sl_host_data_device_manager* host = new sl_host_data_device_manager(); host->ctx = ctx; host->resource = wl_resource_create(client, &wl_data_device_manager_interface, @@ -567,9 +542,9 @@ static void sl_bind_host_data_device_manager(struct wl_client* client, wl_resource_set_implementation(host->resource, &sl_data_device_manager_implementation, host, sl_destroy_host_data_device_manager); - host->proxy = wl_registry_bind( + host->proxy = static_cast(wl_registry_bind( wl_display_get_registry(ctx->display), ctx->data_device_manager->id, - &wl_data_device_manager_interface, ctx->data_device_manager->version); + &wl_data_device_manager_interface, ctx->data_device_manager->version)); wl_data_device_manager_set_user_data(host->proxy, host); } diff --git a/sommelier/sommelier-display.c b/sommelier/sommelier-display.cc similarity index 67% rename from sommelier/sommelier-display.c rename to sommelier/sommelier-display.cc index e5ece4e..f4d5382 100644 --- a/sommelier/sommelier-display.c +++ b/sommelier/sommelier-display.cc @@ -1,8 +1,9 @@ -// Copyright 2018 The Chromium OS Authors. All rights reserved. +// Copyright 2018 The ChromiumOS Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "sommelier.h" +#include "sommelier.h" // NOLINT(build/include_directory) +#include "sommelier-tracing.h" // NOLINT(build/include_directory) #include #include @@ -15,7 +16,9 @@ static void sl_registry_bind(struct wl_client* client, const char* interface, uint32_t version, uint32_t id) { - struct sl_host_registry* host = wl_resource_get_user_data(resource); + TRACE_EVENT("display", "sl_registry_bind"); + struct sl_host_registry* host = + static_cast(wl_resource_get_user_data(resource)); struct sl_global* global; wl_list_for_each(global, &host->ctx->globals, link) { @@ -23,6 +26,7 @@ static void sl_registry_bind(struct wl_client* client, break; } + assert(sl_client_supports_interface(host->ctx, client, global->interface)); assert(&global->link != &host->ctx->globals); assert(version != 0); assert(global->version >= version); @@ -36,7 +40,9 @@ static const struct wl_registry_interface sl_registry_implementation = { static void sl_sync_callback_done(void* data, struct wl_callback* callback, uint32_t serial) { - struct sl_host_callback* host = wl_callback_get_user_data(callback); + TRACE_EVENT("display", "sl_sync_callback_done"); + struct sl_host_callback* host = + static_cast(wl_callback_get_user_data(callback)); wl_callback_send_done(host->resource, serial); wl_resource_destroy(host->resource); @@ -46,48 +52,46 @@ static const struct wl_callback_listener sl_sync_callback_listener = { sl_sync_callback_done}; static void sl_host_callback_destroy(struct wl_resource* resource) { - struct sl_host_callback* host = wl_resource_get_user_data(resource); + struct sl_host_callback* host = + static_cast(wl_resource_get_user_data(resource)); wl_callback_destroy(host->proxy); wl_resource_set_user_data(resource, NULL); - free(host); + delete host; } static void sl_display_sync(struct wl_client* client, struct wl_resource* resource, uint32_t id) { - struct sl_context* ctx = wl_resource_get_user_data(resource); - struct sl_host_callback* host_callback; - - host_callback = malloc(sizeof(*host_callback)); - assert(host_callback); + struct sl_context* ctx = + static_cast(wl_resource_get_user_data(resource)); + struct sl_host_callback* host_callback = new sl_host_callback(); host_callback->resource = wl_resource_create(client, &wl_callback_interface, 1, id); wl_resource_set_implementation(host_callback->resource, NULL, host_callback, sl_host_callback_destroy); host_callback->proxy = wl_display_sync(ctx->display); - wl_callback_set_user_data(host_callback->proxy, host_callback); wl_callback_add_listener(host_callback->proxy, &sl_sync_callback_listener, host_callback); } static void sl_destroy_host_registry(struct wl_resource* resource) { - struct sl_host_registry* host = wl_resource_get_user_data(resource); + struct sl_host_registry* host = + static_cast(wl_resource_get_user_data(resource)); wl_list_remove(&host->link); - free(host); + delete host; } static void sl_display_get_registry(struct wl_client* client, struct wl_resource* resource, uint32_t id) { - struct sl_context* ctx = wl_resource_get_user_data(resource); - struct sl_host_registry* host_registry; + struct sl_context* ctx = + static_cast(wl_resource_get_user_data(resource)); struct sl_global* global; - host_registry = malloc(sizeof(*host_registry)); - assert(host_registry); + struct sl_host_registry* host_registry = new sl_host_registry(); host_registry->ctx = ctx; host_registry->resource = @@ -98,9 +102,11 @@ static void sl_display_get_registry(struct wl_client* client, sl_destroy_host_registry); wl_list_for_each(global, &ctx->globals, link) { - wl_resource_post_event(host_registry->resource, WL_REGISTRY_GLOBAL, - global->name, global->interface->name, - global->version); + if (sl_client_supports_interface(ctx, client, global->interface)) { + wl_resource_post_event(host_registry->resource, WL_REGISTRY_GLOBAL, + global->name, global->interface->name, + global->version); + } } } @@ -120,7 +126,8 @@ static enum wl_iterator_result sl_set_implementation( return WL_ITERATOR_CONTINUE; } -void sl_set_display_implementation(struct sl_context* ctx) { +void sl_set_display_implementation(struct sl_context* ctx, + struct wl_client* client) { // Find display resource and set implementation. - wl_client_for_each_resource(ctx->client, sl_set_implementation, ctx); + wl_client_for_each_resource(client, sl_set_implementation, ctx); } diff --git a/sommelier/sommelier-gaming.cc b/sommelier/sommelier-gaming.cc new file mode 100644 index 0000000..edb543f --- /dev/null +++ b/sommelier/sommelier-gaming.cc @@ -0,0 +1,312 @@ +// Copyright 2020 The ChromiumOS Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sommelier.h" // NOLINT(build/include_directory) +#include "sommelier-tracing.h" // NOLINT(build/include_directory) + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gaming-input-unstable-v2-client-protocol.h" // NOLINT(build/include_directory) + +// Overview of state management via gaming events, in order: +// 1) Acquire gaming seats (in sommelier.cc) +// 2) Add listeners to gaming seats +// 3) Listen for zcr_gaming_seat_v2.gamepad_added to construct a 'default' +// game controller (not currently implemented) +// Calls libevdev_new, libevdev_enable_event_type, +// libevdev_uinput_create_from_device +// 4) Listen for zcr_gaming_seat_v2.gamepad_added_with_device_info to construct +// a custom game controller +// Calls libevdev_new +// 5) Listen for zcr_gamepad_v2.axis_added to fill in a custom game controller +// Calls libevdev_enable_event_type +// 6) Listen for zcr_gamepad_v2.activated to finalize a custom game controller +// Calls libevdev_uinput_create_from_device +// 7) Listen for zcr_gamepad_v2.axis to set frame state for game controller +// Calls libevdev_uinput_write_event +// 8) Listen for zcr_gamepad_v2.button to set frame state for game controller +// Calls libevdev_uinput_write_event +// 9) Listen for zcr_gamepad_v2.frame to emit collected frame +// Calls libevdev_uinput_write_event(EV_SYN) +// 10) Listen for zcr_gamepad_v2.removed to destroy gamepad +// Must handle gamepads in all states of construction or error + +enum GamepadActivationState { + kStateUnknown = 0, // Should not happen + kStatePending = 1, // Constructed, pending axis definition + kStateActivated = 2, // Fully activated + kStateError = 3 // Error occurred during construction; ignore gracefully +}; + +const char kXboxName[] = "Microsoft X-Box One S pad"; +const uint32_t kUsbBus = 0x03; +const uint32_t kXboxVendor = 0x45e; +const uint32_t kXboxProduct = 0x2ea; +const uint32_t kXboxVersion = 0x301; + +// Note: the BT vendor ID for SteelSeries is due to a chipset bug +// and is not an actual claimed Vendor ID. +const uint32_t kSteelSeriesVendorBt = 0x111; +const uint32_t kSteelSeriesProductStratusDuoBt = 0x1431; +const uint32_t kSteelSeriesProductStratusPlusBt = 0x1434; + +const uint32_t kStadiaVendor = 0x18d1; +const uint32_t kStadiaProduct = 0x9400; +const uint32_t kStadiaVersion = 0x111; + +// Note: the majority of protocol errors are treated as non-fatal, and +// are intended to be handled gracefully, as is removal at any +// state of construction or operation. We should expect that +// 'sudden removal' can happen at any time, due to hotplugging +// or unexpected state changes from the wayland server. + +static void sl_internal_gamepad_removed(void* data, + struct zcr_gamepad_v2* gamepad) { + TRACE_EVENT("gaming", "sl_internal_gamepad_removed"); + struct sl_host_gamepad* host_gamepad = (struct sl_host_gamepad*)data; + + assert(host_gamepad->state == kStatePending || + host_gamepad->state == kStateActivated || + host_gamepad->state == kStateError); + + if (host_gamepad->uinput_dev != NULL) + libevdev_uinput_destroy(host_gamepad->uinput_dev); + if (host_gamepad->ev_dev != NULL) + libevdev_free(host_gamepad->ev_dev); + + zcr_gamepad_v2_destroy(gamepad); + + wl_list_remove(&host_gamepad->link); + delete host_gamepad; +} + +static uint32_t remap_axis(struct sl_host_gamepad* host_gamepad, + uint32_t axis) { + if (host_gamepad->axes_quirk) { + if (axis == ABS_Z) + axis = ABS_RX; + else if (axis == ABS_RZ) + axis = ABS_RY; + else if (axis == ABS_BRAKE) + axis = ABS_Z; + else if (axis == ABS_GAS) + axis = ABS_RZ; + } + return axis; +} + +static void sl_internal_gamepad_axis(void* data, + struct zcr_gamepad_v2* gamepad, + uint32_t time, + uint32_t axis, + wl_fixed_t value) { + TRACE_EVENT("gaming", "sl_internal_gamepad_axis"); + struct sl_host_gamepad* host_gamepad = (struct sl_host_gamepad*)data; + + if (host_gamepad->state != kStateActivated) + return; + + axis = remap_axis(host_gamepad, axis); + + // Note: incoming time is ignored, it will be regenerated from current time. + libevdev_uinput_write_event(host_gamepad->uinput_dev, EV_ABS, axis, + wl_fixed_to_double(value)); +} + +static void sl_internal_gamepad_button(void* data, + struct zcr_gamepad_v2* gamepad, + uint32_t time, + uint32_t button, + uint32_t state, + wl_fixed_t analog) { + TRACE_EVENT("gaming", "sl_internal_gamepad_button"); + struct sl_host_gamepad* host_gamepad = (struct sl_host_gamepad*)data; + + if (host_gamepad->state != kStateActivated) + return; + + // Note: Exo wayland server always sends analog==0, only pay attention + // to state. + int value = (state == ZCR_GAMEPAD_V2_BUTTON_STATE_PRESSED) ? 1 : 0; + + // Note: incoming time is ignored, it will be regenerated from current time. + libevdev_uinput_write_event(host_gamepad->uinput_dev, EV_KEY, button, value); +} + +static void sl_internal_gamepad_frame(void* data, + struct zcr_gamepad_v2* gamepad, + uint32_t time) { + TRACE_EVENT("gaming", "sl_internal_gamepad_frame"); + struct sl_host_gamepad* host_gamepad = (struct sl_host_gamepad*)data; + + if (host_gamepad->state != kStateActivated) + return; + + // Note: incoming time is ignored, it will be regenerated from current time. + libevdev_uinput_write_event(host_gamepad->uinput_dev, EV_SYN, SYN_REPORT, 0); +} + +static void sl_internal_gamepad_axis_added(void* data, + struct zcr_gamepad_v2* gamepad, + uint32_t index, + int32_t min_value, + int32_t max_value, + int32_t flat, + int32_t fuzz, + int32_t resolution) { + TRACE_EVENT("gaming", "sl_internal_gamepad_axis_added"); + struct sl_host_gamepad* host_gamepad = (struct sl_host_gamepad*)data; + struct input_absinfo info = {.value = 0, // Does this matter? + .minimum = min_value, + .maximum = max_value, + .fuzz = fuzz, + .flat = flat, + .resolution = resolution}; + + if (host_gamepad->state != kStatePending) { + fprintf(stderr, "error: %s invoked in unexpected state %d\n", __func__, + host_gamepad->state); + host_gamepad->state = kStateError; + return; + } + + index = remap_axis(host_gamepad, index); + + libevdev_enable_event_code(host_gamepad->ev_dev, EV_ABS, index, &info); +} + +static void sl_internal_gamepad_activated(void* data, + struct zcr_gamepad_v2* gamepad) { + TRACE_EVENT("gaming", "sl_internal_gamepad_activated"); + struct sl_host_gamepad* host_gamepad = (struct sl_host_gamepad*)data; + + if (host_gamepad->state != kStatePending) { + fprintf(stderr, "error: %s invoked in unexpected state %d\n", __func__, + host_gamepad->state); + host_gamepad->state = kStateError; + return; + } + + int err = libevdev_uinput_create_from_device(host_gamepad->ev_dev, + LIBEVDEV_UINPUT_OPEN_MANAGED, + &host_gamepad->uinput_dev); + if (err == 0) { + // TODO(kenalba): can we destroy and clean up the ev_dev now? + host_gamepad->state = kStateActivated; + } else { + fprintf(stderr, + "error: libevdev_uinput_create_from_device failed with error %d\n", + err); + host_gamepad->state = kStateError; + } +} + +static void sl_internal_gamepad_vibrator_added( + void* data, + struct zcr_gamepad_v2* gamepad, + struct zcr_gamepad_vibrator_v2* vibrator) { + TRACE_EVENT("gaming", "sl_internal_gamepad_vibrator_added"); + // TODO(kenalba): add vibration logic +} + +static const struct zcr_gamepad_v2_listener sl_internal_gamepad_listener = { + sl_internal_gamepad_removed, sl_internal_gamepad_axis, + sl_internal_gamepad_button, sl_internal_gamepad_frame, + sl_internal_gamepad_axis_added, sl_internal_gamepad_activated, + sl_internal_gamepad_vibrator_added}; + +static void sl_internal_gaming_seat_gamepad_added_with_device_info( + void* data, + struct zcr_gaming_seat_v2* gaming_seat, + struct zcr_gamepad_v2* gamepad, + const char* name, + uint32_t bus, + uint32_t vendor_id, + uint32_t product_id, + uint32_t version) { + TRACE_EVENT("gaming", + "sl_internal_gaming_seat_gamepad_added_with_device_info"); + struct sl_context* ctx = (struct sl_context*)data; + struct sl_host_gamepad* host_gamepad = new sl_host_gamepad(); + wl_list_insert(&ctx->gamepads, &host_gamepad->link); + zcr_gamepad_v2_add_listener(gamepad, &sl_internal_gamepad_listener, + host_gamepad); + + host_gamepad->ctx = ctx; + host_gamepad->state = kStatePending; + host_gamepad->ev_dev = libevdev_new(); + host_gamepad->uinput_dev = NULL; + host_gamepad->axes_quirk = false; + + if (host_gamepad->ev_dev == NULL) { + fprintf(stderr, "error: libevdev_new failed\n"); + host_gamepad->state = kStateError; + return; + } + + // We provide limited remapping at this time. Only moderately XBox360 + // HID compatible controllers are likely to work well. + + if (product_id == kStadiaProduct && vendor_id == kStadiaVendor && + version == kStadiaVersion) { + host_gamepad->axes_quirk = true; + } else if (bus == ZCR_GAMING_SEAT_V2_BUS_TYPE_BLUETOOTH && + vendor_id == kSteelSeriesVendorBt && + (product_id == kSteelSeriesProductStratusDuoBt || + product_id == kSteelSeriesProductStratusPlusBt)) { + host_gamepad->axes_quirk = true; + } + + // Describe a common controller + libevdev_set_name(host_gamepad->ev_dev, kXboxName); + libevdev_set_id_bustype(host_gamepad->ev_dev, kUsbBus); + libevdev_set_id_vendor(host_gamepad->ev_dev, kXboxVendor); + libevdev_set_id_product(host_gamepad->ev_dev, kXboxProduct); + libevdev_set_id_version(host_gamepad->ev_dev, kXboxVersion); + + // Enable common set of buttons + + // Note: Do not enable BTN_TL2 or BTN_TR2, as they will significantly + // change the Linux joydev interpretation of the triggers on ABS_Z/ABS_RZ. + int buttons[] = {BTN_SOUTH, BTN_EAST, BTN_NORTH, BTN_WEST, + BTN_TL, BTN_TR, BTN_THUMBL, BTN_THUMBR, + BTN_SELECT, BTN_START, BTN_MODE}; + + for (unsigned int i = 0; i < ARRAY_SIZE(buttons); i++) + libevdev_enable_event_code(host_gamepad->ev_dev, EV_KEY, buttons[i], NULL); +} // NOLINT(whitespace/indent), lint bug b/173143790 + +// Note: not currently implemented by Exo. +static void sl_internal_gaming_seat_gamepad_added( + void* data, + struct zcr_gaming_seat_v2* gaming_seat, + struct zcr_gamepad_v2* gamepad) { + TRACE_EVENT("gaming", "sl_internal_gaming_seat_gamepad_added"); + fprintf(stderr, + "error: sl_internal_gaming_seat_gamepad_added unimplemented\n"); +} + +static const struct zcr_gaming_seat_v2_listener + sl_internal_gaming_seat_listener = { + sl_internal_gaming_seat_gamepad_added, + sl_internal_gaming_seat_gamepad_added_with_device_info}; + +void sl_gaming_seat_add_listener(struct sl_context* ctx) { + if (ctx->gaming_input_manager && ctx->gaming_input_manager->internal) { + TRACE_EVENT("gaming", "sl_gaming_seat_add_listener"); + // TODO(kenalba): does gaming_seat need to persist in ctx? + struct zcr_gaming_seat_v2* gaming_seat = + zcr_gaming_input_v2_get_gaming_seat(ctx->gaming_input_manager->internal, + ctx->default_seat->proxy); + zcr_gaming_seat_v2_add_listener(gaming_seat, + &sl_internal_gaming_seat_listener, ctx); + } +} diff --git a/sommelier/sommelier-global.cc b/sommelier/sommelier-global.cc new file mode 100644 index 0000000..4c93538 --- /dev/null +++ b/sommelier/sommelier-global.cc @@ -0,0 +1,40 @@ +// Copyright 2021 The ChromiumOS Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sommelier-global.h" // NOLINT(build/include_directory) + +#include + +#include "sommelier.h" // NOLINT(build/include_directory) +#include "sommelier-tracing.h" // NOLINT(build/include_directory) + +struct sl_global* sl_global_create(struct sl_context* ctx, + const struct wl_interface* interface, + int version, + void* data, + wl_global_bind_func_t bind) { + TRACE_EVENT("other", "sl_global_create"); + struct sl_host_registry* registry; + + assert(version > 0); + assert(version <= interface->version); + + struct sl_global* global = static_cast(malloc(sizeof *global)); + assert(global); + + global->ctx = ctx; + global->name = ctx->next_global_id++; + global->interface = interface; + global->version = version; + global->data = data; + global->bind = bind; + wl_list_insert(ctx->globals.prev, &global->link); + + wl_list_for_each(registry, &ctx->registries, link) { + wl_resource_post_event(registry->resource, WL_REGISTRY_GLOBAL, global->name, + global->interface->name, global->version); + } + + return global; +} diff --git a/sommelier/sommelier-global.h b/sommelier/sommelier-global.h new file mode 100644 index 0000000..48f8f3e --- /dev/null +++ b/sommelier/sommelier-global.h @@ -0,0 +1,16 @@ +// Copyright 2021 The ChromiumOS Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef VM_TOOLS_SOMMELIER_SOMMELIER_GLOBAL_H_ +#define VM_TOOLS_SOMMELIER_SOMMELIER_GLOBAL_H_ + +#include + +struct sl_global* sl_global_create(struct sl_context* ctx, + const struct wl_interface* interface, + int version, + void* data, + wl_global_bind_func_t bind); + +#endif // VM_TOOLS_SOMMELIER_SOMMELIER_GLOBAL_H_ diff --git a/sommelier/sommelier-gtk-shell.c b/sommelier/sommelier-gtk-shell.cc similarity index 71% rename from sommelier/sommelier-gtk-shell.c rename to sommelier/sommelier-gtk-shell.cc index ece2203..eb2eb03 100644 --- a/sommelier/sommelier-gtk-shell.c +++ b/sommelier/sommelier-gtk-shell.cc @@ -1,15 +1,19 @@ -// Copyright 2018 The Chromium OS Authors. All rights reserved. +// Copyright 2018 The ChromiumOS Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "sommelier.h" +#include "sommelier.h" // NOLINT(build/include_directory) +#include "sommelier-tracing.h" // NOLINT(build/include_directory) #include #include #include -#include "aura-shell-client-protocol.h" -#include "gtk-shell-server-protocol.h" +#include "aura-shell-client-protocol.h" // NOLINT(build/include_directory) +#include "gtk-shell-server-protocol.h" // NOLINT(build/include_directory) + +#define NATIVE_WAYLAND_APPLICATION_ID_FORMAT \ + "org.chromium.guest_os.%s.wayland.%s" struct sl_host_gtk_shell { struct sl_aura_shell* aura_shell; @@ -36,10 +40,15 @@ static void sl_gtk_surface_set_dbus_properties( const char* window_object_path, const char* application_object_path, const char* unique_bus_name) { - struct sl_host_gtk_surface* host = wl_resource_get_user_data(resource); + struct sl_host_gtk_surface* host = + static_cast(wl_resource_get_user_data(resource)); - zaura_surface_set_application_id(host->proxy, application_id); -} + char* application_id_str = + sl_xasprintf(NATIVE_WAYLAND_APPLICATION_ID_FORMAT, + host->aura_shell->ctx->vm_id, application_id); + + zaura_surface_set_application_id(host->proxy, application_id_str); +} // NOLINT(whitespace/indent) static void sl_gtk_surface_set_modal(struct wl_client* client, struct wl_resource* resource) {} @@ -56,25 +65,24 @@ static const struct gtk_surface1_interface sl_gtk_surface_implementation = { sl_gtk_surface_unset_modal, sl_gtk_surface_present}; static void sl_destroy_host_gtk_surface(struct wl_resource* resource) { - struct sl_host_gtk_surface* host = wl_resource_get_user_data(resource); + struct sl_host_gtk_surface* host = + static_cast(wl_resource_get_user_data(resource)); zaura_surface_destroy(host->proxy); wl_list_remove(&host->link); wl_resource_set_user_data(resource, NULL); - free(host); + delete host; } static void sl_gtk_shell_get_gtk_surface(struct wl_client* client, struct wl_resource* resource, uint32_t id, struct wl_resource* surface_resource) { - struct sl_host_gtk_shell* host = wl_resource_get_user_data(resource); - struct sl_host_surface* host_surface = - wl_resource_get_user_data(surface_resource); - struct sl_host_gtk_surface* host_gtk_surface; - - host_gtk_surface = malloc(sizeof(*host_gtk_surface)); - assert(host_gtk_surface); + struct sl_host_gtk_shell* host = + static_cast(wl_resource_get_user_data(resource)); + struct sl_host_surface* host_surface = static_cast( + wl_resource_get_user_data(surface_resource)); + struct sl_host_gtk_surface* host_gtk_surface = new sl_host_gtk_surface(); wl_list_insert(&host->surfaces, &host_gtk_surface->link); host_gtk_surface->aura_shell = host->aura_shell; @@ -88,10 +96,13 @@ static void sl_gtk_shell_get_gtk_surface(struct wl_client* client, zaura_surface_set_startup_id(host_gtk_surface->proxy, host->startup_id); } +// TODO(b/244651040): when adding changing the startup id format, also add vm_id +// here. static void sl_gtk_shell_set_startup_id(struct wl_client* client, struct wl_resource* resource, const char* startup_id) { - struct sl_host_gtk_shell* host = wl_resource_get_user_data(resource); + struct sl_host_gtk_shell* host = + static_cast(wl_resource_get_user_data(resource)); struct sl_host_gtk_surface* surface; free(host->startup_id); @@ -110,19 +121,22 @@ static const struct gtk_shell1_interface sl_gtk_shell_implementation = { sl_gtk_shell_system_bell}; static void sl_destroy_host_gtk_shell(struct wl_resource* resource) { - struct sl_host_gtk_shell* host = wl_resource_get_user_data(resource); + struct sl_host_gtk_shell* host = + static_cast(wl_resource_get_user_data(resource)); free(host->startup_id); wl_callback_destroy(host->callback); zaura_shell_destroy(host->proxy); wl_resource_set_user_data(resource, NULL); - free(host); + delete host; } static void sl_gtk_shell_callback_done(void* data, struct wl_callback* callback, uint32_t serial) { - struct sl_host_gtk_shell* host = wl_callback_get_user_data(callback); + TRACE_EVENT("shell", "sl_gtk_shell_callback_done"); + struct sl_host_gtk_shell* host = + static_cast(wl_callback_get_user_data(callback)); gtk_shell1_send_capabilities(host->resource, 0); } @@ -135,23 +149,19 @@ static void sl_bind_host_gtk_shell(struct wl_client* client, uint32_t version, uint32_t id) { struct sl_context* ctx = (struct sl_context*)data; - struct sl_host_gtk_shell* host; - - host = malloc(sizeof(*host)); - assert(host); + struct sl_host_gtk_shell* host = new sl_host_gtk_shell(); host->aura_shell = ctx->aura_shell; host->startup_id = NULL; wl_list_init(&host->surfaces); host->resource = wl_resource_create(client, >k_shell1_interface, 1, id); wl_resource_set_implementation(host->resource, &sl_gtk_shell_implementation, host, sl_destroy_host_gtk_shell); - host->proxy = wl_registry_bind(wl_display_get_registry(ctx->display), - ctx->aura_shell->id, &zaura_shell_interface, - ctx->aura_shell->version); + host->proxy = static_cast(wl_registry_bind( + wl_display_get_registry(ctx->display), ctx->aura_shell->id, + &zaura_shell_interface, ctx->aura_shell->version)); zaura_shell_set_user_data(host->proxy, host); host->callback = wl_display_sync(ctx->aura_shell->ctx->display); - wl_callback_set_user_data(host->callback, host); wl_callback_add_listener(host->callback, &sl_gtk_shell_callback_listener, host); } @@ -159,4 +169,4 @@ static void sl_bind_host_gtk_shell(struct wl_client* client, struct sl_global* sl_gtk_shell_global_create(struct sl_context* ctx) { return sl_global_create(ctx, >k_shell1_interface, 1, ctx, sl_bind_host_gtk_shell); -} \ No newline at end of file +} diff --git a/sommelier/sommelier-main.cc b/sommelier/sommelier-main.cc new file mode 100644 index 0000000..3d9a7d0 --- /dev/null +++ b/sommelier/sommelier-main.cc @@ -0,0 +1,9 @@ +// Copyright 2021 The ChromiumOS Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +int real_main(int argc, char** argv); + +int main(int argc, char** argv) { + return real_main(argc, argv); +} diff --git a/sommelier/sommelier-output.c b/sommelier/sommelier-output.c deleted file mode 100644 index 20fc1eb..0000000 --- a/sommelier/sommelier-output.c +++ /dev/null @@ -1,348 +0,0 @@ -// Copyright 2018 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "sommelier.h" - -#include -#include -#include -#include - -#include "aura-shell-client-protocol.h" - -#define MAX_OUTPUT_SCALE 2 - -#define INCH_IN_MM 25.4 - -// Legacy X11 applications use DPI to decide on their scale. This value is what -// the convention for a "normal" scale is. One way to verify the convention is -// to note the DPI of a typical monitor circa ~2005, i.e. 20" 1080p. -#define DEFACTO_DPI 96 - -double sl_output_aura_scale_factor_to_double(int scale_factor) { - // Aura scale factor is an enum that for all currently know values - // is a scale value multipled by 1000. For example, enum value for - // 1.25 scale factor is 1250. - return scale_factor / 1000.0; -} - -int dpi_to_physical_mm(double dpi, int px) { - return px * (INCH_IN_MM / dpi); -} - -void sl_output_get_host_output_state(struct sl_host_output* host, - int* scale, - int* physical_width, - int* physical_height, - int* width, - int* height) { - // The user's chosen zoom level. - double current_scale = - sl_output_aura_scale_factor_to_double(host->current_scale); - - // The scale applied to a screen at the default zoom. I.e. this value - // determines the meaning of "100%" zoom, and how zoom relates to the - // apparent resolution: - // - // apparent_res = native_res / device_scale_factor * current_scale - // - // e.g.: On a device with a DSF of 2.0, 80% zoom really means "apply 1.6x - // scale", and 50% zoom would give you an apparent resolution equal to the - // native one. - double device_scale_factor = - sl_output_aura_scale_factor_to_double(host->device_scale_factor); - - // Optimistically, we will try to apply the scale that the user chose. - // Failing that, we will use the scale set for this wl_output. - double applied_scale = device_scale_factor * current_scale; - if (!host->ctx->aura_shell) { - applied_scale = host->scale_factor; - } - - int target_dpi = DEFACTO_DPI; - if (host->ctx->xwayland) { - // For X11, we must fix the scale to be 1 (since X apps typically can't - // handle scaling). As a result, we adjust the resolution (based on the - // scale we want to apply and sommelier's configuration) and the physical - // dimensions (based on what DPI we want the applications to use). E.g.: - // - Device scale is 1.25x, with 1920x1080 resolution on a 295mm by 165mm - // screen. - // - User chosen zoom is 130% - // - Sommelier is scaled to 0.5 (a.k.a low density). Since ctx->scale also - // has the device scale, it will be 0.625 (i.e. 0.5 * 1.25). - // - We want the DPI to be 120 (i.e. 96 * 1.25) - // - Meaning 0.21 mm/px - // - We report resolution 738x415 (1920x1080 * 0.5 / 1.3) - // - We report dimensions 155mm by 87mm (738x415 * 0.21) - // This is mostly expected, another way of thinking about them is that zoom - // and scale modify the application's understanding of length: - // - Increasing the zoom makes lengths appear longer (i.e. fewer mm to work - // with over the same real length). - // - Scaling the screen does the inverse. - if (scale) - *scale = 1; - *width = host->width * host->ctx->scale / applied_scale; - *height = host->height * host->ctx->scale / applied_scale; - - target_dpi = DEFACTO_DPI * device_scale_factor; - *physical_width = dpi_to_physical_mm(target_dpi, *width); - *physical_height = dpi_to_physical_mm(target_dpi, *height); - } else { - // For wayland, we directly apply the scale which combines the user's chosen - // preference (from aura) and the scale which this sommelier was configured - // for (i.e. based on ctx->scale, which comes from the env/cmd line). - // - // See above comment: ctx->scale already has the device_scale_factor in it, - // so this maths actually looks like: - // - // applied / ctx->scale - // = (current*DSF) / (config*DSF) - // = current / config - // - // E.g. if we configured sommelier to scale everything 0.5x, and the user - // has chosen 130% zoom, we are applying 2.6x scale factor. - int s = MIN(ceil(applied_scale / host->ctx->scale), MAX_OUTPUT_SCALE); - - if (scale) - *scale = s; - *physical_width = host->physical_width; - *physical_height = host->physical_height; - *width = host->width * host->ctx->scale * s / applied_scale; - *height = host->height * host->ctx->scale * s / applied_scale; - target_dpi = (*width * INCH_IN_MM) / *physical_width; - } - - if (host->ctx->dpi.size) { - int adjusted_dpi = *((int*)host->ctx->dpi.data); - int* p; - - // Choose the DPI bucket which is closest to the target DPI which we - // calculated above. - wl_array_for_each(p, &host->ctx->dpi) { - if (abs(*p - target_dpi) < abs(adjusted_dpi - target_dpi)) - adjusted_dpi = *p; - } - - *physical_width = dpi_to_physical_mm(adjusted_dpi, *width); - *physical_height = dpi_to_physical_mm(adjusted_dpi, *height); - } -} - -void sl_output_send_host_output_state(struct sl_host_output* host) { - int scale; - int physical_width; - int physical_height; - int width; - int height; - - sl_output_get_host_output_state(host, &scale, &physical_width, - &physical_height, &width, &height); - - // Use density of internal display for all Xwayland outputs. X11 clients - // typically lack support for dynamically changing density so it's - // preferred to always use the density of the internal display. - if (host->ctx->xwayland) { - struct sl_host_output* output; - - wl_list_for_each(output, &host->ctx->host_outputs, link) { - if (output->internal) { - int internal_width; - int internal_height; - - sl_output_get_host_output_state(output, NULL, &physical_width, - &physical_height, &internal_width, - &internal_height); - - physical_width = (physical_width * width) / internal_width; - physical_height = (physical_height * height) / internal_height; - break; - } - } - } - - // X/Y are best left at origin as managed X windows are kept centered on - // the root window. The result is that all outputs are overlapping and - // pointer events can always be dispatched to the visible region of the - // window. - wl_output_send_geometry(host->resource, 0, 0, physical_width, physical_height, - host->subpixel, host->make, host->model, - host->transform); - wl_output_send_mode(host->resource, host->flags | WL_OUTPUT_MODE_CURRENT, - width, height, host->refresh); - if (wl_resource_get_version(host->resource) >= WL_OUTPUT_SCALE_SINCE_VERSION) - wl_output_send_scale(host->resource, scale); - if (wl_resource_get_version(host->resource) >= WL_OUTPUT_DONE_SINCE_VERSION) - wl_output_send_done(host->resource); -} - -static void sl_output_geometry(void* data, - struct wl_output* output, - int x, - int y, - int physical_width, - int physical_height, - int subpixel, - const char* make, - const char* model, - int transform) { - struct sl_host_output* host = wl_output_get_user_data(output); - - host->x = x; - host->y = y; - host->physical_width = physical_width; - host->physical_height = physical_height; - host->subpixel = subpixel; - free(host->model); - host->model = strdup(model); - free(host->make); - host->make = strdup(make); - host->transform = transform; -} - -static void sl_output_mode(void* data, - struct wl_output* output, - uint32_t flags, - int width, - int height, - int refresh) { - struct sl_host_output* host = wl_output_get_user_data(output); - - host->flags = flags; - host->width = width; - host->height = height; - host->refresh = refresh; -} - -static void sl_output_done(void* data, struct wl_output* output) { - struct sl_host_output* host = wl_output_get_user_data(output); - - // Early out if scale is expected but not yet know. - if (host->expecting_scale) - return; - - sl_output_send_host_output_state(host); - - // Expect scale if aura output exists. - if (host->aura_output) - host->expecting_scale = 1; -} - -static void sl_output_scale(void* data, - struct wl_output* output, - int32_t scale_factor) { - struct sl_host_output* host = wl_output_get_user_data(output); - - host->scale_factor = scale_factor; -} - -static const struct wl_output_listener sl_output_listener = { - sl_output_geometry, sl_output_mode, sl_output_done, sl_output_scale}; - -static void sl_aura_output_scale(void* data, - struct zaura_output* output, - uint32_t flags, - uint32_t scale) { - struct sl_host_output* host = zaura_output_get_user_data(output); - - if (flags & ZAURA_OUTPUT_SCALE_PROPERTY_CURRENT) - host->current_scale = scale; - if (flags & ZAURA_OUTPUT_SCALE_PROPERTY_PREFERRED) - host->preferred_scale = scale; - - host->expecting_scale = 0; -} - -static void sl_aura_output_connection(void* data, - struct zaura_output* output, - uint32_t connection) { - struct sl_host_output* host = zaura_output_get_user_data(output); - - host->internal = connection == ZAURA_OUTPUT_CONNECTION_TYPE_INTERNAL; -} - -static void sl_aura_output_device_scale_factor(void* data, - struct zaura_output* output, - uint32_t device_scale_factor) { - struct sl_host_output* host = zaura_output_get_user_data(output); - - host->device_scale_factor = device_scale_factor; -} - -static const struct zaura_output_listener sl_aura_output_listener = { - sl_aura_output_scale, sl_aura_output_connection, - sl_aura_output_device_scale_factor}; - -static void sl_destroy_host_output(struct wl_resource* resource) { - struct sl_host_output* host = wl_resource_get_user_data(resource); - - if (host->aura_output) - zaura_output_destroy(host->aura_output); - if (wl_output_get_version(host->proxy) >= WL_OUTPUT_RELEASE_SINCE_VERSION) { - wl_output_release(host->proxy); - } else { - wl_output_destroy(host->proxy); - } - wl_resource_set_user_data(resource, NULL); - wl_list_remove(&host->link); - free(host->make); - free(host->model); - free(host); -} - -static void sl_bind_host_output(struct wl_client* client, - void* data, - uint32_t version, - uint32_t id) { - struct sl_output* output = (struct sl_output*)data; - struct sl_context* ctx = output->ctx; - struct sl_host_output* host; - - host = malloc(sizeof(*host)); - assert(host); - host->ctx = ctx; - host->resource = wl_resource_create(client, &wl_output_interface, - MIN(version, output->version), id); - wl_resource_set_implementation(host->resource, NULL, host, - sl_destroy_host_output); - host->proxy = wl_registry_bind(wl_display_get_registry(ctx->display), - output->id, &wl_output_interface, - wl_resource_get_version(host->resource)); - wl_output_set_user_data(host->proxy, host); - wl_output_add_listener(host->proxy, &sl_output_listener, host); - host->aura_output = NULL; - // We assume that first output is internal by default. - host->internal = wl_list_empty(&ctx->host_outputs); - host->x = 0; - host->y = 0; - host->physical_width = 0; - host->physical_height = 0; - host->subpixel = WL_OUTPUT_SUBPIXEL_UNKNOWN; - host->make = strdup("unknown"); - host->model = strdup("unknown"); - host->transform = WL_OUTPUT_TRANSFORM_NORMAL; - host->flags = 0; - host->width = 1024; - host->height = 768; - host->refresh = 60000; - host->scale_factor = 1; - host->current_scale = 1000; - host->preferred_scale = 1000; - host->device_scale_factor = 1000; - host->expecting_scale = 0; - wl_list_insert(ctx->host_outputs.prev, &host->link); - if (ctx->aura_shell) { - host->expecting_scale = 1; - host->internal = 0; - host->aura_output = - zaura_shell_get_aura_output(ctx->aura_shell->internal, host->proxy); - zaura_output_set_user_data(host->aura_output, host); - zaura_output_add_listener(host->aura_output, &sl_aura_output_listener, - host); - } -} - -struct sl_global* sl_output_global_create(struct sl_output* output) { - return sl_global_create(output->ctx, &wl_output_interface, output->version, - output, sl_bind_host_output); -} diff --git a/sommelier/sommelier-output.cc b/sommelier/sommelier-output.cc new file mode 100644 index 0000000..ac9215a --- /dev/null +++ b/sommelier/sommelier-output.cc @@ -0,0 +1,607 @@ +// Copyright 2022 The ChromiumOS Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sommelier.h" // NOLINT(build/include_directory) +#include "sommelier-transform.h" // NOLINT(build/include_directory) + +#include +#include +#include +#include + +#include "aura-shell-client-protocol.h" // NOLINT(build/include_directory) +#include "xdg-output-unstable-v1-client-protocol.h" // NOLINT(build/include_directory) + +#define MAX_OUTPUT_SCALE 2 + +#define INCH_IN_MM 25.4 + +// Legacy X11 applications use DPI to decide on their scale. This value is what +// the convention for a "normal" scale is. One way to verify the convention is +// to note the DPI of a typical monitor circa ~2005, i.e. 20" 1080p. +#define DEFACTO_DPI 96 + +double sl_output_aura_scale_factor_to_double(int scale_factor) { + // Aura scale factor is an enum that for all currently know values + // is a scale value multipled by 1000. For example, enum value for + // 1.25 scale factor is 1250. + return scale_factor / 1000.0; +} + +int dpi_to_physical_mm(double dpi, int px) { + return px * (INCH_IN_MM / dpi); +} + +void sl_output_get_host_output_state(struct sl_host_output* host, + int* scale, + int* physical_width, + int* physical_height, + int* width, + int* height) { + // The user's chosen zoom level. + double current_scale = + sl_output_aura_scale_factor_to_double(host->current_scale); + + // The scale applied to a screen at the default zoom. I.e. this value + // determines the meaning of "100%" zoom, and how zoom relates to the + // apparent resolution: + // + // apparent_res = native_res / device_scale_factor * current_scale + // + // e.g.: On a device with a DSF of 2.0, 80% zoom really means "apply 1.6x + // scale", and 50% zoom would give you an apparent resolution equal to the + // native one. + double device_scale_factor = + sl_output_aura_scale_factor_to_double(host->device_scale_factor); + + // Optimistically, we will try to apply the scale that the user chose. + // Failing that, we will use the scale set for this wl_output. + double applied_scale = device_scale_factor * current_scale; + if (!host->ctx->aura_shell) { + applied_scale = host->scale_factor; + } + + int target_dpi = DEFACTO_DPI; + if (host->ctx->xwayland) { + // For X11, we must fix the scale to be 1 (since X apps typically can't + // handle scaling). As a result, we adjust the resolution (based on the + // scale we want to apply and sommelier's configuration) and the physical + // dimensions (based on what DPI we want the applications to use). E.g.: + // - Device scale is 1.25x, with 1920x1080 resolution on a 295mm by 165mm + // screen. + // - User chosen zoom is 130% + // - Sommelier is scaled to 0.5 (a.k.a low density). Since ctx->scale also + // has the device scale, it will be 0.625 (i.e. 0.5 * 1.25). + // - We want the DPI to be 120 (i.e. 96 * 1.25) + // - Meaning 0.21 mm/px + // - We report resolution 738x415 (1920x1080 * 0.5 / 1.3) + // - We report dimensions 155mm by 87mm (738x415 * 0.21) + // This is mostly expected, another way of thinking about them is that zoom + // and scale modify the application's understanding of length: + // - Increasing the zoom makes lengths appear longer (i.e. fewer mm to work + // with over the same real length). + // - Scaling the screen does the inverse. + if (scale) + *scale = 1; + *width = host->width * host->ctx->scale / applied_scale; + *height = host->height * host->ctx->scale / applied_scale; + + target_dpi = DEFACTO_DPI * device_scale_factor; + *physical_width = dpi_to_physical_mm(target_dpi, *width); + *physical_height = dpi_to_physical_mm(target_dpi, *height); + } else { + // For wayland, we directly apply the scale which combines the user's chosen + // preference (from aura) and the scale which this sommelier was configured + // for (i.e. based on ctx->scale, which comes from the env/cmd line). + // + // See above comment: ctx->scale already has the device_scale_factor in it, + // so this maths actually looks like: + // + // applied / ctx->scale + // = (current*DSF) / (config*DSF) + // = current / config + // + // E.g. if we configured sommelier to scale everything 0.5x, and the user + // has chosen 130% zoom, we are applying 2.6x scale factor. + int s = MIN(ceil(applied_scale / host->ctx->scale), MAX_OUTPUT_SCALE); + + if (scale) + *scale = s; + *physical_width = host->physical_width; + *physical_height = host->physical_height; + *width = host->width * host->ctx->scale * s / applied_scale; + *height = host->height * host->ctx->scale * s / applied_scale; + target_dpi = (*width * INCH_IN_MM) / *physical_width; + } + + if (host->ctx->dpi.size) { + int adjusted_dpi = *(reinterpret_cast(host->ctx->dpi.data)); + + // Choose the DPI bucket which is closest to the target DPI which we + // calculated above. + int* dpi; + sl_array_for_each(dpi, &host->ctx->dpi) { + if (abs(*dpi - target_dpi) < abs(adjusted_dpi - target_dpi)) + adjusted_dpi = *dpi; + } + + *physical_width = dpi_to_physical_mm(adjusted_dpi, *width); + *physical_height = dpi_to_physical_mm(adjusted_dpi, *height); + } +} + +void sl_output_get_logical_dimensions(struct sl_host_output* host, + bool rotated, + int32_t* width, + int32_t* height) { + if (rotated) { + // Pass the dimensions as is (it could be rotated) + *width = host->logical_width; + *height = host->logical_height; + } else { + // The transform here indicates how a window image will be + // rotated when composited. The incoming surface from the + // application will NOT have its dimensions rotated. + // For this reason, in order to calculate the scale factors + // for direct scale, we will need the non rotated logical + // dimensions. + + switch (host->transform) { + case WL_OUTPUT_TRANSFORM_NORMAL: + case WL_OUTPUT_TRANSFORM_180: + case WL_OUTPUT_TRANSFORM_FLIPPED: + case WL_OUTPUT_TRANSFORM_FLIPPED_180: + *width = host->logical_width; + *height = host->logical_height; + break; + + default: + *width = host->logical_height; + *height = host->logical_width; + break; + } + } +} + +void sl_output_init_dimensions_direct(struct sl_host_output* host, + int* out_scale, + int* out_physical_width, + int* out_physical_height, + int* out_width, + int* out_height) { + int32_t virtual_width = host->width; + int32_t virtual_height = host->height; + + // This requires xdg_output_manager, it is assumed that it will be + // available and we will have an appropriate set of logical dimensions + // for this particular output. + assert(host->ctx->viewporter); + assert(host->ctx->xdg_output_manager); + + // The virtual width/height is computed by this function here based + // on the physical width/height + sl_transform_output_dimensions(host->ctx, &virtual_width, &virtual_height); + + host->virt_scale_x = static_cast(virtual_width) / host->width; + host->virt_scale_y = static_cast(virtual_height) / host->height; + + *out_width = virtual_width; + *out_height = virtual_height; + + // Force the scale to 1 + // + // This is reported to the guest through the wl_output protocol. + // This value will signal by how much a compositor will upscale + // all buffers by (1 is no scale). + *out_scale = 1; + + // The physical dimensions (in mm) are the same, regardless + // of the provided scale factor. + *out_physical_width = host->physical_width; + *out_physical_height = host->physical_height; + + // Retrieve the logical dimensions + int32_t logical_width, logical_height; + + sl_output_get_logical_dimensions(host, /*rotated=*/false, &logical_width, + &logical_height); + + // We want to be able to transform from virtual to XDG logical + // coordinates + // Virt to XDG -> div + // XDG to Virt -> mul + host->xdg_scale_x = + static_cast(virtual_width) / static_cast(logical_width); + host->xdg_scale_y = + static_cast(virtual_height) / static_cast(logical_height); + + if (host->internal) { + host->ctx->virt_scale_x = host->virt_scale_x; + host->ctx->virt_scale_y = host->virt_scale_y; + host->ctx->xdg_scale_x = host->xdg_scale_x; + host->ctx->xdg_scale_y = host->xdg_scale_y; + } +} + +void sl_output_get_dimensions_original(struct sl_host_output* host, + int* out_scale, + int* out_physical_width, + int* out_physical_height, + int* out_width, + int* out_height) { + int scale; + int physical_width; + int physical_height; + int width; + int height; + + sl_output_get_host_output_state(host, &scale, &physical_width, + &physical_height, &width, &height); + + // Use density of internal display for all Xwayland outputs. X11 clients + // typically lack support for dynamically changing density so it's + // preferred to always use the density of the internal display. + if (host->ctx->xwayland) { + struct sl_host_output* output; + + wl_list_for_each(output, &host->ctx->host_outputs, link) { + if (output->internal) { + int internal_width; + int internal_height; + + sl_output_get_host_output_state(output, NULL, &physical_width, + &physical_height, &internal_width, + &internal_height); + + physical_width = (physical_width * width) / internal_width; + physical_height = (physical_height * height) / internal_height; + break; + } + } + } + + *out_scale = scale; + *out_physical_width = physical_width; + *out_physical_height = physical_height; + *out_width = width; + *out_height = height; +} + +// Recalculates the virt_x coordinates of outputs when an output is +// add/removed/changed. skip_host is false if the host is being removed +// (as it should not exist in the list already) and true if it's being +// added/changed. +void sl_output_shift_output_x(struct sl_host_output* host, bool skip_host) { + // Outputs are positioned in a line from left to right ordered base on its + // x position. + struct sl_host_output* output; + int next_output_x = 0; + wl_list_for_each(output, &host->ctx->host_outputs, link) { + if (output->virt_x != next_output_x) { + output->virt_x = next_output_x; + // Skipping sending current output's details here if skip host is set + // as they are sent after this method. + if (!skip_host || output != host) { + // scaled_physical_width/height may have not been set yet. + if (!output->scaled_physical_width || !output->scaled_physical_height) { + int scale; + int physical_width; + int physical_height; + int width; + int height; + + if (host->ctx->use_direct_scale) { + sl_output_init_dimensions_direct(host, &scale, &physical_width, + &physical_height, &width, &height); + } else { + sl_output_get_dimensions_original(host, &scale, &physical_width, + &physical_height, &width, + &height); + } + host->scaled_physical_width = physical_width; + host->scaled_physical_height = physical_height; + } + + wl_output_send_geometry( + output->resource, output->virt_x, output->virt_y, + output->scaled_physical_width, output->scaled_physical_height, + output->subpixel, output->make, output->model, output->transform); + if (wl_resource_get_version(output->resource) >= + WL_OUTPUT_DONE_SINCE_VERSION) + wl_output_send_done(output->resource); + } + } + next_output_x += output->width; + } +} + +void sl_output_send_host_output_state(struct sl_host_output* host) { + int scale; + int physical_width; + int physical_height; + int width; + int height; + + if (host->ctx->use_direct_scale) { + sl_output_init_dimensions_direct(host, &scale, &physical_width, + &physical_height, &width, &height); + } else { + sl_output_get_dimensions_original(host, &scale, &physical_width, + &physical_height, &width, &height); + } + + host->scaled_physical_width = physical_width; + host->scaled_physical_height = physical_height; + + // Shift all outputs that are to the right of host to the right if needed. + sl_output_shift_output_x(host, true); + host->virt_y = 0; + wl_output_send_geometry(host->resource, host->virt_x, host->virt_y, + host->scaled_physical_width, + host->scaled_physical_height, host->subpixel, + host->make, host->model, host->transform); + wl_output_send_mode(host->resource, host->flags | WL_OUTPUT_MODE_CURRENT, + width, height, host->refresh); + if (wl_resource_get_version(host->resource) >= WL_OUTPUT_SCALE_SINCE_VERSION) + wl_output_send_scale(host->resource, scale); + if (wl_resource_get_version(host->resource) >= WL_OUTPUT_DONE_SINCE_VERSION) + wl_output_send_done(host->resource); +} + +static void sl_output_geometry(void* data, + struct wl_output* output, + int x, + int y, + int physical_width, + int physical_height, + int subpixel, + const char* make, + const char* model, + int transform) { + struct sl_host_output* host = + static_cast(wl_output_get_user_data(output)); + + host->x = x; + host->y = y; + host->physical_width = physical_width; + host->physical_height = physical_height; + host->subpixel = subpixel; + free(host->model); + host->model = strdup(model); + free(host->make); + host->make = strdup(make); + host->transform = transform; + // host_outputs is sorted by x. Delete then re-insert at the correct position. + wl_list_remove(&host->link); + // Insert at the end by default. If insert_at is not set in the loop, + // hosts's x is larger than all the ones in the list currently. + struct wl_list* insert_at = host->ctx->host_outputs.prev; + struct sl_host_output* iter; + wl_list_for_each(iter, &host->ctx->host_outputs, link) { + if (host->x < iter->x) { + // This is the first output whose x cooridinate is to the right of host, + // therefore insert to the left of it. + insert_at = iter->link.prev; + break; + } + } + wl_list_insert(insert_at, &host->link); +} + +static void sl_output_mode(void* data, + struct wl_output* output, + uint32_t flags, + int width, + int height, + int refresh) { + struct sl_host_output* host = + static_cast(wl_output_get_user_data(output)); + + host->flags = flags; + host->width = width; + host->height = height; + host->refresh = refresh; +} + +static void sl_output_done(void* data, struct wl_output* output) { + struct sl_host_output* host = + static_cast(wl_output_get_user_data(output)); + + // Early out if scale is expected but not yet know. + if (host->expecting_scale) + return; + + sl_output_send_host_output_state(host); + + // Expect scale if aura output exists. + if (host->aura_output) + host->expecting_scale = 1; +} + +static void sl_output_scale(void* data, + struct wl_output* output, + int32_t scale_factor) { + struct sl_host_output* host = + static_cast(wl_output_get_user_data(output)); + + host->scale_factor = scale_factor; +} + +static const struct wl_output_listener sl_output_listener = { + sl_output_geometry, sl_output_mode, sl_output_done, sl_output_scale}; + +static void sl_aura_output_scale(void* data, + struct zaura_output* output, + uint32_t flags, + uint32_t scale) { + struct sl_host_output* host = + static_cast(zaura_output_get_user_data(output)); + + if (flags & ZAURA_OUTPUT_SCALE_PROPERTY_CURRENT) + host->current_scale = scale; + if (flags & ZAURA_OUTPUT_SCALE_PROPERTY_PREFERRED) + host->preferred_scale = scale; + + host->expecting_scale = 0; +} + +static void sl_aura_output_connection(void* data, + struct zaura_output* output, + uint32_t connection) { + struct sl_host_output* host = + static_cast(zaura_output_get_user_data(output)); + + host->internal = connection == ZAURA_OUTPUT_CONNECTION_TYPE_INTERNAL; +} + +static void sl_aura_output_device_scale_factor(void* data, + struct zaura_output* output, + uint32_t device_scale_factor) { + struct sl_host_output* host = + static_cast(zaura_output_get_user_data(output)); + + host->device_scale_factor = device_scale_factor; +} + +static void sl_aura_output_insets(void* data, + struct zaura_output* output, + int top, + int left, + int bottom, + int right) {} + +static void sl_aura_output_logical_transform(void* data, + struct zaura_output* output, + int transform) {} + +static const struct zaura_output_listener sl_aura_output_listener = { + sl_aura_output_scale, sl_aura_output_connection, + sl_aura_output_device_scale_factor, sl_aura_output_insets, + sl_aura_output_logical_transform}; + +static void sl_destroy_host_output(struct wl_resource* resource) { + struct sl_host_output* host = + static_cast(wl_resource_get_user_data(resource)); + + if (host->aura_output) + zaura_output_destroy(host->aura_output); + if (wl_output_get_version(host->proxy) >= WL_OUTPUT_RELEASE_SINCE_VERSION) { + wl_output_release(host->proxy); + } else { + wl_output_destroy(host->proxy); + } + wl_resource_set_user_data(resource, NULL); + wl_list_remove(&host->link); + free(host->make); + free(host->model); + // Shift all outputs to the right of the deleted output to the left. + sl_output_shift_output_x(host, false); + delete host; +} + +static void sl_xdg_output_logical_position( + void* data, struct zxdg_output_v1* zxdg_output_v1, int32_t x, int32_t y) { + struct sl_host_output* host = static_cast( + zxdg_output_v1_get_user_data(zxdg_output_v1)); + host->logical_y = y; + host->logical_x = x; +} + +static void sl_xdg_output_logical_size(void* data, + struct zxdg_output_v1* zxdg_output_v1, + int32_t width, + int32_t height) { + struct sl_host_output* host = static_cast( + zxdg_output_v1_get_user_data(zxdg_output_v1)); + + host->logical_width = width; + host->logical_height = height; + + host->expecting_logical_size = false; +} + +static void sl_xdg_output_done(void* data, + struct zxdg_output_v1* zxdg_output_v1) {} + +static void sl_xdg_output_name(void* data, + struct zxdg_output_v1* zxdg_output_v1, + const char* name) {} + +static void sl_xdg_output_desc(void* data, + struct zxdg_output_v1* zxdg_output_v1, + const char* desc) {} + +static const struct zxdg_output_v1_listener sl_xdg_output_listener = { + sl_xdg_output_logical_position, sl_xdg_output_logical_size, + sl_xdg_output_done, sl_xdg_output_name, sl_xdg_output_desc}; + +static void sl_bind_host_output(struct wl_client* client, + void* data, + uint32_t version, + uint32_t id) { + struct sl_output* output = (struct sl_output*)data; + struct sl_context* ctx = output->ctx; + struct sl_host_output* host = new sl_host_output(); + host->ctx = ctx; + host->resource = wl_resource_create(client, &wl_output_interface, + MIN(version, output->version), id); + wl_resource_set_implementation(host->resource, NULL, host, + sl_destroy_host_output); + host->proxy = static_cast(wl_registry_bind( + wl_display_get_registry(ctx->display), output->id, &wl_output_interface, + wl_resource_get_version(host->resource))); + wl_output_add_listener(host->proxy, &sl_output_listener, host); + output->host_output = host; + host->aura_output = NULL; + // We assume that first output is internal by default. + host->internal = wl_list_empty(&ctx->host_outputs); + host->x = 0; + host->y = 0; + host->virt_x = 0; + host->virt_y = 0; + host->logical_x = 0; + host->logical_y = 0; + host->physical_width = 0; + host->physical_height = 0; + host->scaled_physical_width = 0; + host->scaled_physical_height = 0; + host->subpixel = WL_OUTPUT_SUBPIXEL_UNKNOWN; + host->make = strdup("unknown"); + host->model = strdup("unknown"); + host->transform = WL_OUTPUT_TRANSFORM_NORMAL; + host->flags = 0; + host->width = 1024; + host->height = 768; + host->logical_width = 1024; + host->logical_height = 768; + host->refresh = 60000; + host->scale_factor = 1; + host->current_scale = 1000; + host->preferred_scale = 1000; + host->device_scale_factor = 1000; + host->expecting_scale = 0; + host->expecting_logical_size = false; + wl_list_insert(ctx->host_outputs.prev, &host->link); + if (ctx->aura_shell) { + host->expecting_scale = 1; + host->internal = 0; + host->aura_output = + zaura_shell_get_aura_output(ctx->aura_shell->internal, host->proxy); + zaura_output_add_listener(host->aura_output, &sl_aura_output_listener, + host); + } + + if (ctx->xdg_output_manager) { + host->expecting_logical_size = true; + host->zxdg_output = zxdg_output_manager_v1_get_xdg_output( + ctx->xdg_output_manager->internal, host->proxy); + zxdg_output_v1_add_listener(host->zxdg_output, &sl_xdg_output_listener, + host); + } +} + +struct sl_global* sl_output_global_create(struct sl_output* output) { + return sl_global_create(output->ctx, &wl_output_interface, output->version, + output, sl_bind_host_output); +} diff --git a/sommelier/sommelier-pointer-constraints.c b/sommelier/sommelier-pointer-constraints.cc similarity index 60% rename from sommelier/sommelier-pointer-constraints.c rename to sommelier/sommelier-pointer-constraints.cc index 62dfc88..efd61cd 100644 --- a/sommelier/sommelier-pointer-constraints.c +++ b/sommelier/sommelier-pointer-constraints.cc @@ -1,36 +1,38 @@ -// Copyright 2019 The Chromium OS Authors. All rights reserved. +// Copyright 2019 The ChromiumOS Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "sommelier.h" +#include "sommelier.h" // NOLINT(build/include_directory) #include #include #include #include #include -#include #include +#include #include -#include "pointer-constraints-unstable-v1-server-protocol.h" -#include "pointer-constraints-unstable-v1-client-protocol.h" +#include "pointer-constraints-unstable-v1-client-protocol.h" // NOLINT(build/include_directory) +#include "pointer-constraints-unstable-v1-server-protocol.h" // NOLINT(build/include_directory) -#define SL_HOST_OBJECT(NAME, INTERFACE) \ - struct sl_host_##NAME { \ - struct sl_context* ctx; \ - struct wl_resource* resource; \ - struct INTERFACE* proxy; \ - }; \ - static void sl_destroy_host_##NAME(struct wl_resource* resource) { \ - struct sl_host_##NAME* host = wl_resource_get_user_data(resource); \ - INTERFACE##_destroy(host->proxy); \ - wl_resource_set_user_data(resource, NULL); \ - free(host); \ - } \ - static void sl_##NAME##_destroy(struct wl_client* client, \ - struct wl_resource* resource) { \ - wl_resource_destroy(resource); \ +#define SL_HOST_OBJECT(NAME, INTERFACE) \ + struct sl_host_##NAME { \ + struct sl_context* ctx; \ + struct wl_resource* resource; \ + struct INTERFACE* proxy; \ + }; \ + MAP_STRUCTS(INTERFACE, sl_host_##NAME); \ + static void sl_destroy_host_##NAME(struct wl_resource* resource) { \ + struct sl_host_##NAME* host = \ + static_cast(wl_resource_get_user_data(resource)); \ + INTERFACE##_destroy(host->proxy); \ + wl_resource_set_user_data(resource, NULL); \ + delete host; \ + } \ + static void sl_##NAME##_destroy(struct wl_client* client, \ + struct wl_resource* resource) { \ + wl_resource_destroy(resource); \ } SL_HOST_OBJECT(pointer_constraints, zwp_pointer_constraints_v1); @@ -41,55 +43,38 @@ SL_HOST_OBJECT(confined_pointer, zwp_confined_pointer_v1); static void sl_locked_pointer_locked( void* data, struct zwp_locked_pointer_v1* locked_pointer) { - struct sl_host_locked_pointer* host = - zwp_locked_pointer_v1_get_user_data(locked_pointer); + struct sl_host_locked_pointer* host = static_cast( + zwp_locked_pointer_v1_get_user_data(locked_pointer)); zwp_locked_pointer_v1_send_locked(host->resource); } static void sl_locked_pointer_unlocked( void* data, struct zwp_locked_pointer_v1* locked_pointer) { - struct sl_host_locked_pointer* host = - zwp_locked_pointer_v1_get_user_data(locked_pointer); + struct sl_host_locked_pointer* host = static_cast( + zwp_locked_pointer_v1_get_user_data(locked_pointer)); zwp_locked_pointer_v1_send_unlocked(host->resource); } -static void sl_locked_pointer_set_cursor_position_hint( - struct wl_client* client, - struct wl_resource* resource, - wl_fixed_t surface_x, - wl_fixed_t surface_y) { - struct sl_host_locked_pointer* host = wl_resource_get_user_data(resource); - - zwp_locked_pointer_v1_set_cursor_position_hint(host->proxy, surface_x, - surface_y); -} - -static void sl_locked_pointer_set_region(struct wl_client* client, - struct wl_resource* resource, - struct wl_resource* region) { - struct sl_host_locked_pointer* host = wl_resource_get_user_data(resource); - struct sl_host_region* host_region = - region ? wl_resource_get_user_data(region) : NULL; - - zwp_locked_pointer_v1_set_region(host->proxy, - host_region ? host_region->proxy : NULL); -} - static struct zwp_locked_pointer_v1_listener sl_locked_pointer_listener = { sl_locked_pointer_locked, sl_locked_pointer_unlocked, }; static struct zwp_locked_pointer_v1_interface sl_locked_pointer_implementation = - {sl_locked_pointer_destroy, sl_locked_pointer_set_cursor_position_hint, - sl_locked_pointer_set_region}; + { + sl_locked_pointer_destroy, + ForwardRequest, + ForwardRequest, +}; static void sl_confined_pointer_confined( void* data, struct zwp_confined_pointer_v1* confined_pointer) { struct sl_host_confined_pointer* host = - zwp_confined_pointer_v1_get_user_data(confined_pointer); + static_cast( + zwp_confined_pointer_v1_get_user_data(confined_pointer)); zwp_confined_pointer_v1_send_confined(host->resource); } @@ -97,22 +82,12 @@ static void sl_confined_pointer_confined( static void sl_confined_pointer_unconfined( void* data, struct zwp_confined_pointer_v1* confined_pointer) { struct sl_host_confined_pointer* host = - zwp_confined_pointer_v1_get_user_data(confined_pointer); + static_cast( + zwp_confined_pointer_v1_get_user_data(confined_pointer)); zwp_confined_pointer_v1_send_unconfined(host->resource); } -static void sl_confined_pointer_set_region(struct wl_client* client, - struct wl_resource* resource, - struct wl_resource* region) { - struct sl_host_confined_pointer* host = wl_resource_get_user_data(resource); - struct sl_host_region* host_region = - region ? wl_resource_get_user_data(region) : NULL; - - zwp_confined_pointer_v1_set_region(host->proxy, - host_region ? host_region->proxy : NULL); -} - static struct zwp_confined_pointer_v1_listener sl_confined_pointer_listener = { sl_confined_pointer_confined, sl_confined_pointer_unconfined, @@ -121,7 +96,8 @@ static struct zwp_confined_pointer_v1_listener sl_confined_pointer_listener = { static struct zwp_confined_pointer_v1_interface sl_confined_pointer_implementation = { sl_confined_pointer_destroy, - sl_confined_pointer_set_region, + ForwardRequest, }; static void sl_pointer_constraints_lock_pointer(struct wl_client* client, @@ -132,32 +108,33 @@ static void sl_pointer_constraints_lock_pointer(struct wl_client* client, struct wl_resource* region, uint32_t lifetime) { struct sl_host_pointer_constraints* host = - wl_resource_get_user_data(resource); + static_cast( + wl_resource_get_user_data(resource)); struct wl_resource* locked_pointer_resource = wl_resource_create(client, &zwp_locked_pointer_v1_interface, 1, id); - struct sl_host_locked_pointer* locked_pointer_host; - struct sl_host_surface* host_surface = wl_resource_get_user_data(surface); - struct sl_host_pointer* host_pointer = wl_resource_get_user_data(pointer); + struct sl_host_surface* host_surface = + static_cast(wl_resource_get_user_data(surface)); + struct sl_host_pointer* host_pointer = + static_cast(wl_resource_get_user_data(pointer)); struct sl_host_region* host_region = - region ? wl_resource_get_user_data(region) : NULL; + region ? static_cast(wl_resource_get_user_data(region)) + : NULL; - locked_pointer_host = malloc(sizeof(struct sl_host_locked_pointer)); - assert(locked_pointer_host); + struct sl_host_locked_pointer* locked_pointer_host = + new sl_host_locked_pointer(); locked_pointer_host->resource = locked_pointer_resource; locked_pointer_host->ctx = host->ctx; locked_pointer_host->proxy = zwp_pointer_constraints_v1_lock_pointer( - host->ctx->pointer_constraints->internal, host_surface->proxy, - host_pointer->proxy, host_region ? host_region->proxy : NULL, lifetime); + host->proxy, host_surface->proxy, host_pointer->proxy, + host_region ? host_region->proxy : NULL, lifetime); wl_resource_set_implementation( locked_pointer_resource, &sl_locked_pointer_implementation, locked_pointer_host, sl_destroy_host_locked_pointer); - zwp_locked_pointer_v1_set_user_data(locked_pointer_host->proxy, - locked_pointer_host); zwp_locked_pointer_v1_add_listener(locked_pointer_host->proxy, &sl_locked_pointer_listener, locked_pointer_host); -} +} // NOLINT(whitespace/indent) static void sl_pointer_constraints_confine_pointer(struct wl_client* client, struct wl_resource* resource, @@ -167,32 +144,33 @@ static void sl_pointer_constraints_confine_pointer(struct wl_client* client, struct wl_resource* region, uint32_t lifetime) { struct sl_host_pointer_constraints* host = - wl_resource_get_user_data(resource); + static_cast( + wl_resource_get_user_data(resource)); struct wl_resource* confined_pointer_resource = wl_resource_create(client, &zwp_confined_pointer_v1_interface, 1, id); - struct sl_host_confined_pointer* confined_pointer_host; - struct sl_host_surface* host_surface = wl_resource_get_user_data(surface); - struct sl_host_pointer* host_pointer = wl_resource_get_user_data(pointer); + struct sl_host_surface* host_surface = + static_cast(wl_resource_get_user_data(surface)); + struct sl_host_pointer* host_pointer = + static_cast(wl_resource_get_user_data(pointer)); struct sl_host_region* host_region = - region ? wl_resource_get_user_data(region) : NULL; + region ? static_cast(wl_resource_get_user_data(region)) + : NULL; - confined_pointer_host = malloc(sizeof(struct sl_host_confined_pointer)); - assert(confined_pointer_host); + struct sl_host_confined_pointer* confined_pointer_host = + new sl_host_confined_pointer(); confined_pointer_host->resource = confined_pointer_resource; confined_pointer_host->ctx = host->ctx; confined_pointer_host->proxy = zwp_pointer_constraints_v1_confine_pointer( - host->ctx->pointer_constraints->internal, host_surface->proxy, - host_pointer->proxy, host_region ? host_region->proxy : NULL, lifetime); + host->proxy, host_surface->proxy, host_pointer->proxy, + host_region ? host_region->proxy : NULL, lifetime); wl_resource_set_implementation( confined_pointer_resource, &sl_confined_pointer_implementation, confined_pointer_host, sl_destroy_host_confined_pointer); - zwp_confined_pointer_v1_set_user_data(confined_pointer_host->proxy, - confined_pointer_host); zwp_confined_pointer_v1_add_listener(confined_pointer_host->proxy, &sl_confined_pointer_listener, confined_pointer_host); -} +} // NOLINT(whitespace/indent) static struct zwp_pointer_constraints_v1_interface sl_pointer_constraints_implementation = { @@ -207,20 +185,18 @@ static void sl_bind_host_pointer_constraints(struct wl_client* client, uint32_t id) { struct sl_context* ctx = (struct sl_context*)data; struct sl_pointer_constraints* pointer_constraints = ctx->pointer_constraints; - struct sl_host_pointer_constraints* host = - malloc(sizeof(struct sl_host_pointer_constraints)); + struct sl_host_pointer_constraints* host = new sl_host_pointer_constraints(); - assert(host); host->ctx = ctx; host->resource = wl_resource_create(client, &zwp_pointer_constraints_v1_interface, 1, id); wl_resource_set_implementation(host->resource, &sl_pointer_constraints_implementation, host, sl_destroy_host_pointer_constraints); - host->proxy = wl_registry_bind(wl_display_get_registry(ctx->display), - pointer_constraints->id, - &zwp_pointer_constraints_v1_interface, - wl_resource_get_version(host->resource)); + host->proxy = static_cast(wl_registry_bind( + wl_display_get_registry(ctx->display), pointer_constraints->id, + &zwp_pointer_constraints_v1_interface, + wl_resource_get_version(host->resource))); zwp_pointer_constraints_v1_set_user_data(host->proxy, host); } diff --git a/sommelier/sommelier-relative-pointer-manager.c b/sommelier/sommelier-relative-pointer-manager.cc similarity index 65% rename from sommelier/sommelier-relative-pointer-manager.c rename to sommelier/sommelier-relative-pointer-manager.cc index d1a80c2..1a7ffd3 100644 --- a/sommelier/sommelier-relative-pointer-manager.c +++ b/sommelier/sommelier-relative-pointer-manager.cc @@ -1,19 +1,21 @@ -// Copyright 2019 The Chromium OS Authors. All rights reserved. +// Copyright 2019 The ChromiumOS Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "sommelier.h" +#include "sommelier.h" // NOLINT(build/include_directory) #include +#include #include #include #include #include #include #include +#include -#include "relative-pointer-unstable-v1-server-protocol.h" -#include "relative-pointer-unstable-v1-client-protocol.h" +#include "relative-pointer-unstable-v1-client-protocol.h" // NOLINT(build/include_directory) +#include "relative-pointer-unstable-v1-server-protocol.h" // NOLINT(build/include_directory) struct sl_host_relative_pointer_manager { struct sl_context* ctx; @@ -27,6 +29,14 @@ struct sl_host_relative_pointer { struct zwp_relative_pointer_v1* proxy; }; +// Like ceil(), but strictly increases the magnitude of the input value (i.e. +// trunc() but for increasing the magnitude). +wl_fixed_t magnitude_ceil(wl_fixed_t val) { + double temp = wl_fixed_to_double(val); + temp = temp > 0 ? ceil(temp) : floor(temp); + return wl_fixed_from_double(temp); +} + static void sl_relative_pointer_relative_motion( void* data, struct zwp_relative_pointer_v1* relative_pointer, @@ -37,18 +47,29 @@ static void sl_relative_pointer_relative_motion( wl_fixed_t dx_unaccel, wl_fixed_t dy_unaccel) { struct sl_host_relative_pointer* host = - zwp_relative_pointer_v1_get_user_data(relative_pointer); + static_cast( + zwp_relative_pointer_v1_get_user_data(relative_pointer)); + + // Unfortunately, many x11 toolkits truncate RawMotion events. We force them + // to interpret cursor movement by rounding to the next greater-magnitude + // value. + if (host->ctx->xwayland) { + dx_unaccel = magnitude_ceil(dx_unaccel); + dy_unaccel = magnitude_ceil(dy_unaccel); + } zwp_relative_pointer_v1_send_relative_motion( host->resource, utime_hi, utime_lo, dx, dy, dx_unaccel, dy_unaccel); } static void sl_destroy_host_relative_pointer(struct wl_resource* resource) { - struct sl_host_relative_pointer* host = wl_resource_get_user_data(resource); + struct sl_host_relative_pointer* host = + static_cast( + wl_resource_get_user_data(resource)); zwp_relative_pointer_v1_destroy(host->proxy); wl_resource_set_user_data(resource, NULL); - free(host); + delete host; } static void sl_relative_pointer_destroy(struct wl_client* client, @@ -68,11 +89,12 @@ static struct zwp_relative_pointer_v1_interface static void sl_destroy_host_relative_pointer_manager( struct wl_resource* resource) { struct sl_host_relative_pointer_manager* host = - wl_resource_get_user_data(resource); + static_cast( + wl_resource_get_user_data(resource)); zwp_relative_pointer_manager_v1_destroy(host->proxy); wl_resource_set_user_data(resource, NULL); - free(host); + delete host; } static void sl_relative_pointer_manager_destroy(struct wl_client* client, @@ -86,28 +108,26 @@ static void sl_relative_pointer_manager_get_relative_pointer( uint32_t id, struct wl_resource* pointer) { struct sl_host_relative_pointer_manager* host = - wl_resource_get_user_data(resource); - struct sl_host_pointer* host_pointer = wl_resource_get_user_data(pointer); + static_cast( + wl_resource_get_user_data(resource)); + struct sl_host_pointer* host_pointer = + static_cast(wl_resource_get_user_data(pointer)); struct wl_resource* relative_pointer_resource = wl_resource_create(client, &zwp_relative_pointer_v1_interface, 1, id); - struct sl_host_relative_pointer* relative_pointer_host; - - relative_pointer_host = malloc(sizeof(*relative_pointer_host)); - assert(relative_pointer_host); + struct sl_host_relative_pointer* relative_pointer_host = + new sl_host_relative_pointer(); relative_pointer_host->resource = relative_pointer_resource; relative_pointer_host->ctx = host->ctx; relative_pointer_host->proxy = - zwp_relative_pointer_manager_v1_get_relative_pointer( - host->ctx->relative_pointer_manager->internal, host_pointer->proxy); + zwp_relative_pointer_manager_v1_get_relative_pointer(host->proxy, + host_pointer->proxy); wl_resource_set_implementation( relative_pointer_resource, &sl_relative_pointer_implementation, relative_pointer_host, sl_destroy_host_relative_pointer); - zwp_relative_pointer_v1_set_user_data(relative_pointer_host->proxy, - relative_pointer_host); zwp_relative_pointer_v1_add_listener(relative_pointer_host->proxy, &sl_relative_pointer_listener, relative_pointer_host); -} +} // NOLINT(whitespace/indent) static struct zwp_relative_pointer_manager_v1_interface sl_relative_pointer_manager_implementation = { @@ -122,20 +142,18 @@ static void sl_bind_host_relative_pointer_manager(struct wl_client* client, struct sl_context* ctx = (struct sl_context*)data; struct sl_relative_pointer_manager* relative_pointer_manager = ctx->relative_pointer_manager; - struct sl_host_relative_pointer_manager* host; - - host = malloc(sizeof(*host)); - assert(host); + struct sl_host_relative_pointer_manager* host = + new sl_host_relative_pointer_manager(); host->ctx = ctx; host->resource = wl_resource_create( client, &zwp_relative_pointer_manager_v1_interface, 1, id); wl_resource_set_implementation( host->resource, &sl_relative_pointer_manager_implementation, host, sl_destroy_host_relative_pointer_manager); - host->proxy = wl_registry_bind(wl_display_get_registry(ctx->display), - relative_pointer_manager->id, - &zwp_relative_pointer_manager_v1_interface, - wl_resource_get_version(host->resource)); + host->proxy = static_cast(wl_registry_bind( + wl_display_get_registry(ctx->display), relative_pointer_manager->id, + &zwp_relative_pointer_manager_v1_interface, + wl_resource_get_version(host->resource))); zwp_relative_pointer_manager_v1_set_user_data(host->proxy, host); } diff --git a/sommelier/sommelier-seat.c b/sommelier/sommelier-seat.cc similarity index 72% rename from sommelier/sommelier-seat.c rename to sommelier/sommelier-seat.cc index 7e4d4d2..0d5217f 100644 --- a/sommelier/sommelier-seat.c +++ b/sommelier/sommelier-seat.cc @@ -1,17 +1,20 @@ -// Copyright 2018 The Chromium OS Authors. All rights reserved. +// Copyright 2018 The ChromiumOS Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "sommelier.h" +#include "sommelier.h" // NOLINT(build/include_directory) +#include "sommelier-transform.h" // NOLINT(build/include_directory) #include +#include #include #include #include #include #include +#include -#include "keyboard-extension-unstable-v1-client-protocol.h" +#include "keyboard-extension-unstable-v1-client-protocol.h" // NOLINT(build/include_directory) struct sl_host_keyboard { struct sl_seat* seat; @@ -35,6 +38,7 @@ struct sl_host_touch { struct wl_resource* resource; struct wl_touch* proxy; struct wl_resource* focus_resource; + struct sl_host_surface* focus_surface; struct wl_listener focus_resource_listener; }; @@ -44,21 +48,26 @@ static void sl_host_pointer_set_cursor(struct wl_client* client, struct wl_resource* surface_resource, int32_t hotspot_x, int32_t hotspot_y) { - struct sl_host_pointer* host = wl_resource_get_user_data(resource); + struct sl_host_pointer* host = + static_cast(wl_resource_get_user_data(resource)); struct sl_host_surface* host_surface = NULL; - double scale = host->seat->ctx->scale; + + int32_t hsx = hotspot_x; + int32_t hsy = hotspot_y; if (surface_resource) { - host_surface = wl_resource_get_user_data(surface_resource); + host_surface = static_cast( + wl_resource_get_user_data(surface_resource)); host_surface->has_role = 1; if (host_surface->contents_width && host_surface->contents_height) wl_surface_commit(host_surface->proxy); } + sl_transform_guest_to_host(host->seat->ctx, nullptr, &hsx, &hsy); + wl_pointer_set_cursor(host->proxy, serial, - host_surface ? host_surface->proxy : NULL, - hotspot_x / scale, hotspot_y / scale); -} + host_surface ? host_surface->proxy : NULL, hsx, hsy); +} // NOLINT(whitespace/indent) static void sl_host_pointer_release(struct wl_client* client, struct wl_resource* resource) { @@ -70,8 +79,8 @@ static const struct wl_pointer_interface sl_pointer_implementation = { static void sl_set_last_event_serial(struct wl_resource* surface_resource, uint32_t serial) { - struct sl_host_surface* host_surface = - wl_resource_get_user_data(surface_resource); + struct sl_host_surface* host_surface = static_cast( + wl_resource_get_user_data(surface_resource)); host_surface->last_event_serial = serial; } @@ -83,6 +92,8 @@ static void sl_pointer_set_focus(struct sl_host_pointer* host, wl_fixed_t y) { struct wl_resource* surface_resource = host_surface ? host_surface->resource : NULL; + wl_fixed_t ix = x; + wl_fixed_t iy = y; if (surface_resource == host->focus_resource) return; @@ -94,10 +105,9 @@ static void sl_pointer_set_focus(struct sl_host_pointer* host, wl_list_init(&host->focus_resource_listener.link); host->focus_resource = surface_resource; host->focus_serial = serial; + host->focus_surface = host_surface; if (surface_resource) { - double scale = host->seat->ctx->scale; - if (host->seat->ctx->xwayland) { // Make sure focus surface is on top before sending enter event. sl_restack_windows(host->seat->ctx, wl_resource_get_id(surface_resource)); @@ -107,8 +117,8 @@ static void sl_pointer_set_focus(struct sl_host_pointer* host, wl_resource_add_destroy_listener(surface_resource, &host->focus_resource_listener); - wl_pointer_send_enter(host->resource, serial, surface_resource, x * scale, - y * scale); + sl_transform_host_to_guest_fixed(host->seat->ctx, host_surface, &ix, &iy); + wl_pointer_send_enter(host->resource, serial, surface_resource, ix, iy); } } @@ -118,9 +128,11 @@ static void sl_pointer_enter(void* data, struct wl_surface* surface, wl_fixed_t x, wl_fixed_t y) { - struct sl_host_pointer* host = wl_pointer_get_user_data(pointer); + struct sl_host_pointer* host = + static_cast(wl_pointer_get_user_data(pointer)); struct sl_host_surface* host_surface = - surface ? wl_surface_get_user_data(surface) : NULL; + surface ? static_cast(wl_surface_get_user_data(surface)) + : NULL; if (!host_surface) return; @@ -130,13 +142,14 @@ static void sl_pointer_enter(void* data, if (host->focus_resource) sl_set_last_event_serial(host->focus_resource, serial); host->seat->last_serial = serial; -} +} // NOLINT(whitespace/indent) static void sl_pointer_leave(void* data, struct wl_pointer* pointer, uint32_t serial, struct wl_surface* surface) { - struct sl_host_pointer* host = wl_pointer_get_user_data(pointer); + struct sl_host_pointer* host = + static_cast(wl_pointer_get_user_data(pointer)); sl_pointer_set_focus(host, serial, NULL, 0, 0); } @@ -146,10 +159,15 @@ static void sl_pointer_motion(void* data, uint32_t time, wl_fixed_t x, wl_fixed_t y) { - struct sl_host_pointer* host = wl_pointer_get_user_data(pointer); - double scale = host->seat->ctx->scale; + struct sl_host_pointer* host = + static_cast(wl_pointer_get_user_data(pointer)); - wl_pointer_send_motion(host->resource, time, x * scale, y * scale); + wl_fixed_t mx = x; + wl_fixed_t my = y; + + sl_transform_host_to_guest_fixed(host->seat->ctx, host->focus_surface, &mx, + &my); + wl_pointer_send_motion(host->resource, time, mx, my); } static void sl_pointer_button(void* data, @@ -158,7 +176,8 @@ static void sl_pointer_button(void* data, uint32_t time, uint32_t button, uint32_t state) { - struct sl_host_pointer* host = wl_pointer_get_user_data(pointer); + struct sl_host_pointer* host = + static_cast(wl_pointer_get_user_data(pointer)); wl_pointer_send_button(host->resource, serial, time, button, state); @@ -172,14 +191,51 @@ static void sl_pointer_axis(void* data, uint32_t time, uint32_t axis, wl_fixed_t value) { - struct sl_host_pointer* host = wl_pointer_get_user_data(pointer); - double scale = host->seat->ctx->scale; + struct sl_host_pointer* host = + static_cast(wl_pointer_get_user_data(pointer)); + wl_fixed_t svalue = value; - wl_pointer_send_axis(host->resource, time, axis, value * scale); + sl_transform_host_to_guest_fixed(host->seat->ctx, nullptr, &svalue, axis); + + host->time = time; + host->axis_delta[axis] += svalue; } static void sl_pointer_frame(void* data, struct wl_pointer* pointer) { - struct sl_host_pointer* host = wl_pointer_get_user_data(pointer); + struct sl_host_pointer* host = + static_cast(wl_pointer_get_user_data(pointer)); + + // Many X apps (e.g. VS Code, Firefox, Chromium) only allow scrolls to happen + // in multiples of 5 units. This value comes from the smooth scrolling + // extension of X, which says that 5 smooth scroll units is equal to 1 tick of + // discrete scrolling. + // + // To avoid the experience of scrolling and seeing nothing happen, we replace + // scroll amounts < 5 with 5 units, for discrete scrolls for X apps + // only. Other clients should continue to see smaller scrolls as they can + // handle them correctly, and for non-discrete scrolling (such as touch-pads) + // we don't want to do this because it would lead to erratic jumps. + const int kDiscreteScrollUnit = 5; + + for (int axis = 0; axis < 2; axis++) { + if (host->axis_discrete[axis] != 0) { + wl_pointer_send_axis_discrete(host->resource, axis, + host->axis_discrete[axis]); + + double axis_delta = wl_fixed_to_double(host->axis_delta[axis]); + if (fabs(axis_delta) < kDiscreteScrollUnit && host->seat->ctx->xwayland) { + axis_delta = copysign(kDiscreteScrollUnit, axis_delta); + host->axis_delta[axis] = wl_fixed_from_double(axis_delta); + } + } + if (host->axis_delta[axis] != 0) { + wl_pointer_send_axis(host->resource, host->time, axis, + host->axis_delta[axis]); + } + + host->axis_delta[axis] = wl_fixed_from_int(0); + host->axis_discrete[axis] = 0; + } wl_pointer_send_frame(host->resource); } @@ -187,7 +243,8 @@ static void sl_pointer_frame(void* data, struct wl_pointer* pointer) { void sl_pointer_axis_source(void* data, struct wl_pointer* pointer, uint32_t axis_source) { - struct sl_host_pointer* host = wl_pointer_get_user_data(pointer); + struct sl_host_pointer* host = + static_cast(wl_pointer_get_user_data(pointer)); wl_pointer_send_axis_source(host->resource, axis_source); } @@ -196,7 +253,8 @@ static void sl_pointer_axis_stop(void* data, struct wl_pointer* pointer, uint32_t time, uint32_t axis) { - struct sl_host_pointer* host = wl_pointer_get_user_data(pointer); + struct sl_host_pointer* host = + static_cast(wl_pointer_get_user_data(pointer)); wl_pointer_send_axis_stop(host->resource, time, axis); } @@ -205,9 +263,10 @@ static void sl_pointer_axis_discrete(void* data, struct wl_pointer* pointer, uint32_t axis, int32_t discrete) { - struct sl_host_pointer* host = wl_pointer_get_user_data(pointer); + struct sl_host_pointer* host = + static_cast(wl_pointer_get_user_data(pointer)); - wl_pointer_send_axis_discrete(host->resource, axis, discrete); + host->axis_discrete[axis] += discrete; } static const struct wl_pointer_listener sl_pointer_listener = { @@ -228,7 +287,8 @@ static void sl_keyboard_keymap(void* data, uint32_t format, int32_t fd, uint32_t size) { - struct sl_host_keyboard* host = wl_keyboard_get_user_data(keyboard); + struct sl_host_keyboard* host = + static_cast(wl_keyboard_get_user_data(keyboard)); wl_keyboard_send_keymap(host->resource, format, fd, size); @@ -241,7 +301,9 @@ static void sl_keyboard_keymap(void* data, xkb_keymap_unref(host->keymap); host->keymap = xkb_keymap_new_from_string( - host->seat->ctx->xkb_context, data, XKB_KEYMAP_FORMAT_TEXT_V1, 0); + host->seat->ctx->xkb_context, static_cast(data), + XKB_KEYMAP_FORMAT_TEXT_V1, + static_cast(XKB_KEYMAP_COMPILE_NO_FLAGS)); assert(host->keymap); munmap(data, size); @@ -291,9 +353,11 @@ static void sl_keyboard_enter(void* data, uint32_t serial, struct wl_surface* surface, struct wl_array* keys) { - struct sl_host_keyboard* host = wl_keyboard_get_user_data(keyboard); + struct sl_host_keyboard* host = + static_cast(wl_keyboard_get_user_data(keyboard)); struct sl_host_surface* host_surface = - surface ? wl_surface_get_user_data(surface) : NULL; + surface ? static_cast(wl_surface_get_user_data(surface)) + : NULL; if (!host_surface) return; @@ -302,13 +366,14 @@ static void sl_keyboard_enter(void* data, sl_keyboard_set_focus(host, serial, host_surface, keys); host->seat->last_serial = serial; -} +} // NOLINT(whitespace/indent) static void sl_keyboard_leave(void* data, struct wl_keyboard* keyboard, uint32_t serial, struct wl_surface* surface) { - struct sl_host_keyboard* host = wl_keyboard_get_user_data(keyboard); + struct sl_host_keyboard* host = + static_cast(wl_keyboard_get_user_data(keyboard)); struct wl_array array; wl_array_init(&array); @@ -318,11 +383,11 @@ static void sl_keyboard_leave(void* data, static int sl_array_set_add(struct wl_array* array, uint32_t key) { uint32_t* k; - wl_array_for_each(k, array) { + sl_array_for_each(k, array) { if (*k == key) return 0; } - k = wl_array_add(array, sizeof(key)); + k = static_cast(wl_array_add(array, sizeof(key))); assert(k); *k = key; return 1; @@ -331,9 +396,10 @@ static int sl_array_set_add(struct wl_array* array, uint32_t key) { static int sl_array_set_remove(struct wl_array* array, uint32_t key) { uint32_t* k; - wl_array_for_each(k, array) { + sl_array_for_each(k, array) { if (*k == key) { - uint32_t* end = (uint32_t*)((char*)array->data + array->size); + uint32_t* end = reinterpret_cast( + reinterpret_cast(array->data) + array->size); *k = *(end - 1); array->size -= sizeof(*k); @@ -349,8 +415,9 @@ static void sl_keyboard_key(void* data, uint32_t time, uint32_t key, uint32_t state) { - struct sl_host_keyboard* host = wl_keyboard_get_user_data(keyboard); - int handled = 1; + struct sl_host_keyboard* host = + static_cast(wl_keyboard_get_user_data(keyboard)); + bool handled = true; if (state == WL_KEYBOARD_KEY_STATE_PRESSED) { if (host->state) { @@ -365,16 +432,25 @@ static void sl_keyboard_key(void* data, symbol = symbols[0]; wl_list_for_each(accelerator, &host->seat->ctx->accelerators, link) { - if (host->modifiers != accelerator->modifiers) - continue; - if (symbol != accelerator->symbol) - continue; - - handled = 0; - break; + if (host->modifiers == accelerator->modifiers && + xkb_keysym_to_lower(symbol) == accelerator->symbol) { + handled = false; + break; + } + } + if (host->seat->ctx->host_focus_window && + !(host->seat->ctx->host_focus_window->fullscreen || + host->seat->ctx->host_focus_window->compositor_fullscreen)) { + wl_list_for_each(accelerator, &host->seat->ctx->windowed_accelerators, + link) { + if (host->modifiers == accelerator->modifiers && + xkb_keysym_to_lower(symbol) == accelerator->symbol) { + handled = false; + break; + } + } } } - // Forward key pressed event if it should be handled and not // already pressed. if (handled) { @@ -407,7 +483,8 @@ static void sl_keyboard_modifiers(void* data, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) { - struct sl_host_keyboard* host = wl_keyboard_get_user_data(keyboard); + struct sl_host_keyboard* host = + static_cast(wl_keyboard_get_user_data(keyboard)); xkb_mod_mask_t mask; wl_keyboard_send_modifiers(host->resource, serial, mods_depressed, @@ -423,7 +500,8 @@ static void sl_keyboard_modifiers(void* data, xkb_state_update_mask(host->state, mods_depressed, mods_latched, mods_locked, 0, 0, group); mask = xkb_state_serialize_mods( - host->state, XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_LATCHED); + host->state, static_cast(XKB_STATE_MODS_DEPRESSED | + XKB_STATE_MODS_LATCHED)); host->modifiers = 0; if (mask & host->control_mask) host->modifiers |= CONTROL_MASK; @@ -437,7 +515,8 @@ static void sl_keyboard_repeat_info(void* data, struct wl_keyboard* keyboard, int32_t rate, int32_t delay) { - struct sl_host_keyboard* host = wl_keyboard_get_user_data(keyboard); + struct sl_host_keyboard* host = + static_cast(wl_keyboard_get_user_data(keyboard)); wl_keyboard_send_repeat_info(host->resource, rate, delay); } @@ -462,10 +541,14 @@ static void sl_host_touch_down(void* data, int32_t id, wl_fixed_t x, wl_fixed_t y) { - struct sl_host_touch* host = wl_touch_get_user_data(touch); + struct sl_host_touch* host = + static_cast(wl_touch_get_user_data(touch)); struct sl_host_surface* host_surface = - surface ? wl_surface_get_user_data(surface) : NULL; - double scale = host->seat->ctx->scale; + surface ? static_cast(wl_surface_get_user_data(surface)) + : NULL; + + wl_fixed_t ix = x; + wl_fixed_t iy = y; if (!host_surface) return; @@ -474,6 +557,7 @@ static void sl_host_touch_down(void* data, wl_list_remove(&host->focus_resource_listener.link); wl_list_init(&host->focus_resource_listener.link); host->focus_resource = host_surface->resource; + host->focus_surface = host_surface; wl_resource_add_destroy_listener(host_surface->resource, &host->focus_resource_listener); } @@ -485,24 +569,27 @@ static void sl_host_touch_down(void* data, sl_roundtrip(host->seat->ctx); } + sl_transform_host_to_guest_fixed(host->seat->ctx, host_surface, &ix, &iy); wl_touch_send_down(host->resource, serial, time, host_surface->resource, id, - x * scale, y * scale); + ix, iy); if (host->focus_resource) sl_set_last_event_serial(host->focus_resource, serial); host->seat->last_serial = serial; -} +} // NOLINT(whitespace/indent) static void sl_host_touch_up(void* data, struct wl_touch* touch, uint32_t serial, uint32_t time, int32_t id) { - struct sl_host_touch* host = wl_touch_get_user_data(touch); + struct sl_host_touch* host = + static_cast(wl_touch_get_user_data(touch)); wl_list_remove(&host->focus_resource_listener.link); wl_list_init(&host->focus_resource_listener.link); host->focus_resource = NULL; + host->focus_surface = NULL; wl_touch_send_up(host->resource, serial, time, id); @@ -517,20 +604,26 @@ static void sl_host_touch_motion(void* data, int32_t id, wl_fixed_t x, wl_fixed_t y) { - struct sl_host_touch* host = wl_touch_get_user_data(touch); - double scale = host->seat->ctx->scale; + struct sl_host_touch* host = + static_cast(wl_touch_get_user_data(touch)); + wl_fixed_t ix = x; + wl_fixed_t iy = y; - wl_touch_send_motion(host->resource, time, id, x * scale, y * scale); + sl_transform_host_to_guest_fixed(host->seat->ctx, host->focus_surface, &ix, + &iy); + wl_touch_send_motion(host->resource, time, id, ix, iy); } static void sl_host_touch_frame(void* data, struct wl_touch* touch) { - struct sl_host_touch* host = wl_touch_get_user_data(touch); + struct sl_host_touch* host = + static_cast(wl_touch_get_user_data(touch)); wl_touch_send_frame(host->resource); } static void sl_host_touch_cancel(void* data, struct wl_touch* touch) { - struct sl_host_touch* host = wl_touch_get_user_data(touch); + struct sl_host_touch* host = + static_cast(wl_touch_get_user_data(touch)); wl_touch_send_cancel(host->resource); } @@ -540,7 +633,8 @@ static const struct wl_touch_listener sl_touch_listener = { sl_host_touch_frame, sl_host_touch_cancel}; static void sl_destroy_host_pointer(struct wl_resource* resource) { - struct sl_host_pointer* host = wl_resource_get_user_data(resource); + struct sl_host_pointer* host = + static_cast(wl_resource_get_user_data(resource)); if (wl_pointer_get_version(host->proxy) >= WL_POINTER_RELEASE_SINCE_VERSION) { wl_pointer_release(host->proxy); @@ -549,7 +643,7 @@ static void sl_destroy_host_pointer(struct wl_resource* resource) { } wl_list_remove(&host->focus_resource_listener.link); wl_resource_set_user_data(resource, NULL); - free(host); + delete host; } static void sl_pointer_focus_resource_destroyed(struct wl_listener* listener, @@ -563,11 +657,9 @@ static void sl_pointer_focus_resource_destroyed(struct wl_listener* listener, static void sl_host_seat_get_host_pointer(struct wl_client* client, struct wl_resource* resource, uint32_t id) { - struct sl_host_seat* host = wl_resource_get_user_data(resource); - struct sl_host_pointer* host_pointer; - - host_pointer = malloc(sizeof(*host_pointer)); - assert(host_pointer); + struct sl_host_seat* host = + static_cast(wl_resource_get_user_data(resource)); + struct sl_host_pointer* host_pointer = new sl_host_pointer(); host_pointer->seat = host->seat; host_pointer->resource = wl_resource_create( @@ -576,18 +668,24 @@ static void sl_host_seat_get_host_pointer(struct wl_client* client, &sl_pointer_implementation, host_pointer, sl_destroy_host_pointer); host_pointer->proxy = wl_seat_get_pointer(host->proxy); - wl_pointer_set_user_data(host_pointer->proxy, host_pointer); wl_pointer_add_listener(host_pointer->proxy, &sl_pointer_listener, host_pointer); wl_list_init(&host_pointer->focus_resource_listener.link); host_pointer->focus_resource_listener.notify = sl_pointer_focus_resource_destroyed; host_pointer->focus_resource = NULL; + host_pointer->focus_surface = NULL; host_pointer->focus_serial = 0; + host_pointer->time = 0; + host_pointer->axis_delta[0] = wl_fixed_from_int(0); + host_pointer->axis_delta[1] = wl_fixed_from_int(0); + host_pointer->axis_discrete[0] = 0; + host_pointer->axis_discrete[1] = 0; } static void sl_destroy_host_keyboard(struct wl_resource* resource) { - struct sl_host_keyboard* host = wl_resource_get_user_data(resource); + struct sl_host_keyboard* host = + static_cast(wl_resource_get_user_data(resource)); if (host->extended_keyboard_proxy) zcr_extended_keyboard_v1_destroy(host->extended_keyboard_proxy); @@ -607,7 +705,7 @@ static void sl_destroy_host_keyboard(struct wl_resource* resource) { wl_list_remove(&host->focus_resource_listener.link); wl_resource_set_user_data(resource, NULL); - free(host); + delete host; } static void sl_keyboard_focus_resource_destroyed(struct wl_listener* listener, @@ -623,11 +721,9 @@ static void sl_keyboard_focus_resource_destroyed(struct wl_listener* listener, static void sl_host_seat_get_host_keyboard(struct wl_client* client, struct wl_resource* resource, uint32_t id) { - struct sl_host_seat* host = wl_resource_get_user_data(resource); - struct sl_host_keyboard* host_keyboard; - - host_keyboard = malloc(sizeof(*host_keyboard)); - assert(host_keyboard); + struct sl_host_seat* host = + static_cast(wl_resource_get_user_data(resource)); + struct sl_host_keyboard* host_keyboard = new sl_host_keyboard(); host_keyboard->seat = host->seat; host_keyboard->resource = wl_resource_create( @@ -636,7 +732,6 @@ static void sl_host_seat_get_host_keyboard(struct wl_client* client, &sl_keyboard_implementation, host_keyboard, sl_destroy_host_keyboard); host_keyboard->proxy = wl_seat_get_keyboard(host->proxy); - wl_keyboard_set_user_data(host_keyboard->proxy, host_keyboard); wl_keyboard_add_listener(host_keyboard->proxy, &sl_keyboard_listener, host_keyboard); wl_list_init(&host_keyboard->focus_resource_listener.link); @@ -663,7 +758,8 @@ static void sl_host_seat_get_host_keyboard(struct wl_client* client, } static void sl_destroy_host_touch(struct wl_resource* resource) { - struct sl_host_touch* host = wl_resource_get_user_data(resource); + struct sl_host_touch* host = + static_cast(wl_resource_get_user_data(resource)); if (wl_touch_get_version(host->proxy) >= WL_TOUCH_RELEASE_SINCE_VERSION) { wl_touch_release(host->proxy); @@ -671,7 +767,7 @@ static void sl_destroy_host_touch(struct wl_resource* resource) { wl_touch_destroy(host->proxy); } wl_resource_set_user_data(resource, NULL); - free(host); + delete host; } static void sl_touch_focus_resource_destroyed(struct wl_listener* listener, @@ -682,16 +778,15 @@ static void sl_touch_focus_resource_destroyed(struct wl_listener* listener, wl_list_remove(&host->focus_resource_listener.link); wl_list_init(&host->focus_resource_listener.link); host->focus_resource = NULL; + host->focus_surface = NULL; } static void sl_host_seat_get_host_touch(struct wl_client* client, struct wl_resource* resource, uint32_t id) { - struct sl_host_seat* host = wl_resource_get_user_data(resource); - struct sl_host_touch* host_touch; - - host_touch = malloc(sizeof(*host_touch)); - assert(host_touch); + struct sl_host_seat* host = + static_cast(wl_resource_get_user_data(resource)); + struct sl_host_touch* host_touch = new sl_host_touch(); host_touch->seat = host->seat; host_touch->resource = wl_resource_create( @@ -699,35 +794,30 @@ static void sl_host_seat_get_host_touch(struct wl_client* client, wl_resource_set_implementation(host_touch->resource, &sl_touch_implementation, host_touch, sl_destroy_host_touch); host_touch->proxy = wl_seat_get_touch(host->proxy); - wl_touch_set_user_data(host_touch->proxy, host_touch); wl_touch_add_listener(host_touch->proxy, &sl_touch_listener, host_touch); wl_list_init(&host_touch->focus_resource_listener.link); host_touch->focus_resource_listener.notify = sl_touch_focus_resource_destroyed; host_touch->focus_resource = NULL; -} - -static void sl_host_seat_release(struct wl_client* client, - struct wl_resource* resource) { - struct sl_host_seat* host = wl_resource_get_user_data(resource); - - wl_seat_release(host->proxy); + host_touch->focus_surface = NULL; } static const struct wl_seat_interface sl_seat_implementation = { sl_host_seat_get_host_pointer, sl_host_seat_get_host_keyboard, - sl_host_seat_get_host_touch, sl_host_seat_release}; + sl_host_seat_get_host_touch, ForwardRequest}; static void sl_seat_capabilities(void* data, struct wl_seat* seat, uint32_t capabilities) { - struct sl_host_seat* host = wl_seat_get_user_data(seat); + struct sl_host_seat* host = + static_cast(wl_seat_get_user_data(seat)); wl_seat_send_capabilities(host->resource, capabilities); } static void sl_seat_name(void* data, struct wl_seat* seat, const char* name) { - struct sl_host_seat* host = wl_seat_get_user_data(seat); + struct sl_host_seat* host = + static_cast(wl_seat_get_user_data(seat)); if (wl_resource_get_version(host->resource) >= WL_SEAT_NAME_SINCE_VERSION) wl_seat_send_name(host->resource, name); @@ -737,7 +827,8 @@ static const struct wl_seat_listener sl_seat_listener = {sl_seat_capabilities, sl_seat_name}; static void sl_destroy_host_seat(struct wl_resource* resource) { - struct sl_host_seat* host = wl_resource_get_user_data(resource); + struct sl_host_seat* host = + static_cast(wl_resource_get_user_data(resource)); sl_host_seat_removed(host); @@ -746,7 +837,7 @@ static void sl_destroy_host_seat(struct wl_resource* resource) { else wl_seat_destroy(host->proxy); wl_resource_set_user_data(resource, NULL); - free(host); + delete host; } static void sl_bind_host_seat(struct wl_client* client, @@ -754,19 +845,15 @@ static void sl_bind_host_seat(struct wl_client* client, uint32_t version, uint32_t id) { struct sl_seat* seat = (struct sl_seat*)data; - struct sl_host_seat* host; - - host = malloc(sizeof(*host)); - assert(host); + struct sl_host_seat* host = new sl_host_seat(); host->seat = seat; host->resource = wl_resource_create(client, &wl_seat_interface, MIN(version, seat->version), id); wl_resource_set_implementation(host->resource, &sl_seat_implementation, host, sl_destroy_host_seat); - host->proxy = wl_registry_bind(wl_display_get_registry(seat->ctx->display), - seat->id, &wl_seat_interface, - wl_resource_get_version(host->resource)); - wl_seat_set_user_data(host->proxy, host); + host->proxy = static_cast(wl_registry_bind( + wl_display_get_registry(seat->ctx->display), seat->id, &wl_seat_interface, + wl_resource_get_version(host->resource))); wl_seat_add_listener(host->proxy, &sl_seat_listener, host); sl_host_seat_added(host); diff --git a/sommelier/sommelier-shell.c b/sommelier/sommelier-shell.c deleted file mode 100644 index f316706..0000000 --- a/sommelier/sommelier-shell.c +++ /dev/null @@ -1,234 +0,0 @@ -// Copyright 2018 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "sommelier.h" - -#include -#include -#include - -struct sl_host_shell_surface { - struct wl_resource* resource; - struct wl_shell_surface* proxy; -}; - -struct sl_host_shell { - struct sl_shell* shell; - struct wl_resource* resource; - struct wl_shell* proxy; -}; - -static void sl_shell_surface_pong(struct wl_client* client, - struct wl_resource* resource, - uint32_t serial) { - struct sl_host_shell_surface* host = wl_resource_get_user_data(resource); - - wl_shell_surface_pong(host->proxy, serial); -} - -static void sl_shell_surface_move(struct wl_client* client, - struct wl_resource* resource, - struct wl_resource* seat_resource, - uint32_t serial) { - struct sl_host_shell_surface* host = wl_resource_get_user_data(resource); - struct sl_host_seat* host_seat = wl_resource_get_user_data(seat_resource); - - wl_shell_surface_move(host->proxy, host_seat->proxy, serial); -} - -static void sl_shell_surface_resize(struct wl_client* client, - struct wl_resource* resource, - struct wl_resource* seat_resource, - uint32_t serial, - uint32_t edges) { - struct sl_host_shell_surface* host = wl_resource_get_user_data(resource); - struct sl_host_seat* host_seat = wl_resource_get_user_data(seat_resource); - - wl_shell_surface_resize(host->proxy, host_seat->proxy, serial, edges); -} - -static void sl_shell_surface_set_toplevel(struct wl_client* client, - struct wl_resource* resource) { - struct sl_host_shell_surface* host = wl_resource_get_user_data(resource); - - wl_shell_surface_set_toplevel(host->proxy); -} - -static void sl_shell_surface_set_transient(struct wl_client* client, - struct wl_resource* resource, - struct wl_resource* parent_resource, - int32_t x, - int32_t y, - uint32_t flags) { - struct sl_host_shell_surface* host = wl_resource_get_user_data(resource); - struct sl_host_surface* host_parent = - wl_resource_get_user_data(parent_resource); - - wl_shell_surface_set_transient(host->proxy, host_parent->proxy, x, y, flags); -} - -static void sl_shell_surface_set_fullscreen( - struct wl_client* client, - struct wl_resource* resource, - uint32_t method, - uint32_t framerate, - struct wl_resource* output_resource) { - struct sl_host_shell_surface* host = wl_resource_get_user_data(resource); - struct sl_host_output* host_output = - output_resource ? wl_resource_get_user_data(output_resource) : NULL; - - wl_shell_surface_set_fullscreen(host->proxy, method, framerate, - host_output ? host_output->proxy : NULL); -} - -static void sl_shell_surface_set_popup(struct wl_client* client, - struct wl_resource* resource, - struct wl_resource* seat_resource, - uint32_t serial, - struct wl_resource* parent_resource, - int32_t x, - int32_t y, - uint32_t flags) { - struct sl_host_shell_surface* host = wl_resource_get_user_data(resource); - struct sl_host_seat* host_seat = wl_resource_get_user_data(seat_resource); - struct sl_host_surface* host_parent = - wl_resource_get_user_data(parent_resource); - - wl_shell_surface_set_popup(host->proxy, host_seat->proxy, serial, - host_parent->proxy, x, y, flags); -} - -static void sl_shell_surface_set_maximized( - struct wl_client* client, - struct wl_resource* resource, - struct wl_resource* output_resource) { - struct sl_host_shell_surface* host = wl_resource_get_user_data(resource); - struct sl_host_output* host_output = - output_resource ? wl_resource_get_user_data(output_resource) : NULL; - - wl_shell_surface_set_maximized(host->proxy, - host_output ? host_output->proxy : NULL); -} - -static void sl_shell_surface_set_title(struct wl_client* client, - struct wl_resource* resource, - const char* title) { - struct sl_host_shell_surface* host = wl_resource_get_user_data(resource); - - wl_shell_surface_set_title(host->proxy, title); -} - -static void sl_shell_surface_set_class(struct wl_client* client, - struct wl_resource* resource, - const char* clazz) { - struct sl_host_shell_surface* host = wl_resource_get_user_data(resource); - - wl_shell_surface_set_class(host->proxy, clazz); -} - -static const struct wl_shell_surface_interface sl_shell_surface_implementation = - {sl_shell_surface_pong, sl_shell_surface_move, - sl_shell_surface_resize, sl_shell_surface_set_toplevel, - sl_shell_surface_set_transient, sl_shell_surface_set_fullscreen, - sl_shell_surface_set_popup, sl_shell_surface_set_maximized, - sl_shell_surface_set_title, sl_shell_surface_set_class}; - -static void sl_shell_surface_ping(void* data, - struct wl_shell_surface* shell_surface, - uint32_t serial) { - struct sl_host_shell_surface* host = - wl_shell_surface_get_user_data(shell_surface); - - wl_shell_surface_send_ping(host->resource, serial); -} - -static void sl_shell_surface_configure(void* data, - struct wl_shell_surface* shell_surface, - uint32_t edges, - int32_t width, - int32_t height) { - struct sl_host_shell_surface* host = - wl_shell_surface_get_user_data(shell_surface); - - wl_shell_surface_send_configure(host->resource, edges, width, height); -} - -static void sl_shell_surface_popup_done( - void* data, struct wl_shell_surface* shell_surface) { - struct sl_host_shell_surface* host = - wl_shell_surface_get_user_data(shell_surface); - - wl_shell_surface_send_popup_done(host->resource); -} - -static const struct wl_shell_surface_listener sl_shell_surface_listener = { - sl_shell_surface_ping, sl_shell_surface_configure, - sl_shell_surface_popup_done}; - -static void sl_destroy_host_shell_surface(struct wl_resource* resource) { - struct sl_host_shell_surface* host = wl_resource_get_user_data(resource); - - wl_shell_surface_destroy(host->proxy); - wl_resource_set_user_data(resource, NULL); - free(host); -} - -static void sl_host_shell_get_shell_surface( - struct wl_client* client, - struct wl_resource* resource, - uint32_t id, - struct wl_resource* surface_resource) { - struct sl_host_shell* host = wl_resource_get_user_data(resource); - struct sl_host_surface* host_surface = - wl_resource_get_user_data(surface_resource); - struct sl_host_shell_surface* host_shell_surface; - - host_shell_surface = malloc(sizeof(*host_shell_surface)); - assert(host_shell_surface); - host_shell_surface->resource = - wl_resource_create(client, &wl_shell_surface_interface, 1, id); - wl_resource_set_implementation( - host_shell_surface->resource, &sl_shell_surface_implementation, - host_shell_surface, sl_destroy_host_shell_surface); - host_shell_surface->proxy = - wl_shell_get_shell_surface(host->proxy, host_surface->proxy); - wl_shell_surface_set_user_data(host_shell_surface->proxy, host_shell_surface); - wl_shell_surface_add_listener(host_shell_surface->proxy, - &sl_shell_surface_listener, host_shell_surface); - host_surface->has_role = 1; -} - -static const struct wl_shell_interface sl_shell_implementation = { - sl_host_shell_get_shell_surface}; - -static void sl_destroy_host_shell(struct wl_resource* resource) { - struct sl_host_shell* host = wl_resource_get_user_data(resource); - - wl_shell_destroy(host->proxy); - wl_resource_set_user_data(resource, NULL); - free(host); -} - -static void sl_bind_host_shell(struct wl_client* client, - void* data, - uint32_t version, - uint32_t id) { - struct sl_context* ctx = (struct sl_context*)data; - struct sl_host_shell* host; - - host = malloc(sizeof(*host)); - assert(host); - host->shell = ctx->shell; - host->resource = wl_resource_create(client, &wl_shell_interface, 1, id); - wl_resource_set_implementation(host->resource, &sl_shell_implementation, host, - sl_destroy_host_shell); - host->proxy = wl_registry_bind(wl_display_get_registry(ctx->display), - ctx->shell->id, &wl_shell_interface, - wl_resource_get_version(host->resource)); - wl_shell_set_user_data(host->proxy, host); -} - -struct sl_global* sl_shell_global_create(struct sl_context* ctx) { - return sl_global_create(ctx, &wl_shell_interface, 1, ctx, sl_bind_host_shell); -} \ No newline at end of file diff --git a/sommelier/sommelier-shell.cc b/sommelier/sommelier-shell.cc new file mode 100644 index 0000000..dfbff82 --- /dev/null +++ b/sommelier/sommelier-shell.cc @@ -0,0 +1,135 @@ +// Copyright 2018 The ChromiumOS Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sommelier.h" // NOLINT(build/include_directory) +#include "sommelier-tracing.h" // NOLINT(build/include_directory) + +#include +#include +#include + +struct sl_host_shell_surface { + struct wl_resource* resource; + struct wl_shell_surface* proxy; +}; +MAP_STRUCTS(wl_shell_surface, sl_host_shell_surface); + +struct sl_host_shell { + struct sl_shell* shell; + struct wl_resource* resource; + struct wl_shell* proxy; +}; + +static const struct wl_shell_surface_interface sl_shell_surface_implementation = + { + ForwardRequest, + ForwardRequest, + ForwardRequest, + ForwardRequest, + ForwardRequest, + ForwardRequest, + ForwardRequest, + ForwardRequest, + ForwardRequest, + ForwardRequest, +}; + +static void sl_shell_surface_ping(void* data, + struct wl_shell_surface* shell_surface, + uint32_t serial) { + struct sl_host_shell_surface* host = static_cast( + wl_shell_surface_get_user_data(shell_surface)); + + wl_shell_surface_send_ping(host->resource, serial); +} + +static void sl_shell_surface_configure(void* data, + struct wl_shell_surface* shell_surface, + uint32_t edges, + int32_t width, + int32_t height) { + TRACE_EVENT("shell", "sl_shell_surface_configure"); + struct sl_host_shell_surface* host = static_cast( + wl_shell_surface_get_user_data(shell_surface)); + + wl_shell_surface_send_configure(host->resource, edges, width, height); +} + +static void sl_shell_surface_popup_done( + void* data, struct wl_shell_surface* shell_surface) { + struct sl_host_shell_surface* host = static_cast( + wl_shell_surface_get_user_data(shell_surface)); + + wl_shell_surface_send_popup_done(host->resource); +} + +static const struct wl_shell_surface_listener sl_shell_surface_listener = { + sl_shell_surface_ping, sl_shell_surface_configure, + sl_shell_surface_popup_done}; + +static void sl_destroy_host_shell_surface(struct wl_resource* resource) { + struct sl_host_shell_surface* host = + static_cast(wl_resource_get_user_data(resource)); + + wl_shell_surface_destroy(host->proxy); + wl_resource_set_user_data(resource, NULL); + delete host; +} + +static void sl_host_shell_get_shell_surface( + struct wl_client* client, + struct wl_resource* resource, + uint32_t id, + struct wl_resource* surface_resource) { + struct sl_host_shell* host = + static_cast(wl_resource_get_user_data(resource)); + struct sl_host_surface* host_surface = static_cast( + wl_resource_get_user_data(surface_resource)); + struct sl_host_shell_surface* host_shell_surface = + new sl_host_shell_surface(); + host_shell_surface->resource = + wl_resource_create(client, &wl_shell_surface_interface, 1, id); + wl_resource_set_implementation( + host_shell_surface->resource, &sl_shell_surface_implementation, + host_shell_surface, sl_destroy_host_shell_surface); + host_shell_surface->proxy = + wl_shell_get_shell_surface(host->proxy, host_surface->proxy); + wl_shell_surface_add_listener(host_shell_surface->proxy, + &sl_shell_surface_listener, host_shell_surface); + host_surface->has_role = 1; +} // NOLINT(whitespace/indent) + +static const struct wl_shell_interface sl_shell_implementation = { + sl_host_shell_get_shell_surface}; + +static void sl_destroy_host_shell(struct wl_resource* resource) { + struct sl_host_shell* host = + static_cast(wl_resource_get_user_data(resource)); + + wl_shell_destroy(host->proxy); + wl_resource_set_user_data(resource, NULL); + delete host; +} + +static void sl_bind_host_shell(struct wl_client* client, + void* data, + uint32_t version, + uint32_t id) { + struct sl_context* ctx = (struct sl_context*)data; + struct sl_host_shell* host = new sl_host_shell(); + assert(host); + host->shell = ctx->shell; + host->resource = wl_resource_create(client, &wl_shell_interface, 1, id); + wl_resource_set_implementation(host->resource, &sl_shell_implementation, host, + sl_destroy_host_shell); + host->proxy = static_cast(wl_registry_bind( + wl_display_get_registry(ctx->display), ctx->shell->id, + &wl_shell_interface, wl_resource_get_version(host->resource))); + wl_shell_set_user_data(host->proxy, host); +} + +struct sl_global* sl_shell_global_create(struct sl_context* ctx) { + return sl_global_create(ctx, &wl_shell_interface, 1, ctx, sl_bind_host_shell); +} diff --git a/sommelier/sommelier-subcompositor.c b/sommelier/sommelier-subcompositor.cc similarity index 57% rename from sommelier/sommelier-subcompositor.c rename to sommelier/sommelier-subcompositor.cc index 2bc04e2..0fc15b0 100644 --- a/sommelier/sommelier-subcompositor.c +++ b/sommelier/sommelier-subcompositor.cc @@ -1,8 +1,9 @@ -// Copyright 2018 The Chromium OS Authors. All rights reserved. +// Copyright 2018 The ChromiumOS Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "sommelier.h" +#include "sommelier.h" // NOLINT(build/include_directory) +#include "sommelier-transform.h" // NOLINT(build/include_directory) #include #include @@ -19,6 +20,7 @@ struct sl_host_subsurface { struct wl_resource* resource; struct wl_subsurface* proxy; }; +MAP_STRUCTS(wl_subsurface, sl_host_subsurface); static void sl_subsurface_destroy(struct wl_client* client, struct wl_resource* resource) { @@ -29,57 +31,31 @@ static void sl_subsurface_set_position(struct wl_client* client, struct wl_resource* resource, int32_t x, int32_t y) { - struct sl_host_subsurface* host = wl_resource_get_user_data(resource); - double scale = host->ctx->scale; + struct sl_host_subsurface* host = + static_cast(wl_resource_get_user_data(resource)); + int32_t ix = x; + int32_t iy = y; - wl_subsurface_set_position(host->proxy, x / scale, y / scale); -} - -static void sl_subsurface_place_above(struct wl_client* client, - struct wl_resource* resource, - struct wl_resource* sibling_resource) { - struct sl_host_subsurface* host = wl_resource_get_user_data(resource); - struct sl_host_surface* host_sibling = - wl_resource_get_user_data(sibling_resource); - - wl_subsurface_place_above(host->proxy, host_sibling->proxy); -} - -static void sl_subsurface_place_below(struct wl_client* client, - struct wl_resource* resource, - struct wl_resource* sibling_resource) { - struct sl_host_subsurface* host = wl_resource_get_user_data(resource); - struct sl_host_surface* host_sibling = - wl_resource_get_user_data(sibling_resource); - - wl_subsurface_place_below(host->proxy, host_sibling->proxy); -} - -static void sl_subsurface_set_sync(struct wl_client* client, - struct wl_resource* resource) { - struct sl_host_subsurface* host = wl_resource_get_user_data(resource); - - wl_subsurface_set_sync(host->proxy); -} - -static void sl_subsurface_set_desync(struct wl_client* client, - struct wl_resource* resource) { - struct sl_host_subsurface* host = wl_resource_get_user_data(resource); - - wl_subsurface_set_desync(host->proxy); + sl_transform_guest_to_host(host->ctx, nullptr, &ix, &iy); + wl_subsurface_set_position(host->proxy, ix, iy); } static const struct wl_subsurface_interface sl_subsurface_implementation = { - sl_subsurface_destroy, sl_subsurface_set_position, - sl_subsurface_place_above, sl_subsurface_place_below, - sl_subsurface_set_sync, sl_subsurface_set_desync}; + sl_subsurface_destroy, + sl_subsurface_set_position, + ForwardRequest, + ForwardRequest, + ForwardRequest, + ForwardRequest, +}; static void sl_destroy_host_subsurface(struct wl_resource* resource) { - struct sl_host_subsurface* host = wl_resource_get_user_data(resource); + struct sl_host_subsurface* host = + static_cast(wl_resource_get_user_data(resource)); wl_subsurface_destroy(host->proxy); wl_resource_set_user_data(resource, NULL); - free(host); + delete host; } static void sl_subcompositor_destroy(struct wl_client* client, @@ -93,15 +69,13 @@ static void sl_subcompositor_get_subsurface( uint32_t id, struct wl_resource* surface_resource, struct wl_resource* parent_resource) { - struct sl_host_subcompositor* host = wl_resource_get_user_data(resource); - struct sl_host_surface* host_surface = - wl_resource_get_user_data(surface_resource); + struct sl_host_subcompositor* host = + static_cast(wl_resource_get_user_data(resource)); + struct sl_host_surface* host_surface = static_cast( + wl_resource_get_user_data(surface_resource)); struct sl_host_surface* host_parent = - wl_resource_get_user_data(parent_resource); - struct sl_host_subsurface* host_subsurface; - - host_subsurface = malloc(sizeof(*host_subsurface)); - assert(host_subsurface); + static_cast(wl_resource_get_user_data(parent_resource)); + struct sl_host_subsurface* host_subsurface = new sl_host_subsurface(); host_subsurface->ctx = host->ctx; host_subsurface->resource = @@ -113,17 +87,18 @@ static void sl_subcompositor_get_subsurface( host->proxy, host_surface->proxy, host_parent->proxy); wl_subsurface_set_user_data(host_subsurface->proxy, host_subsurface); host_surface->has_role = 1; -} +} // NOLINT(whitespace/indent) static const struct wl_subcompositor_interface sl_subcompositor_implementation = {sl_subcompositor_destroy, sl_subcompositor_get_subsurface}; static void sl_destroy_host_subcompositor(struct wl_resource* resource) { - struct sl_host_subcompositor* host = wl_resource_get_user_data(resource); + struct sl_host_subcompositor* host = + static_cast(wl_resource_get_user_data(resource)); wl_subcompositor_destroy(host->proxy); wl_resource_set_user_data(resource, NULL); - free(host); + delete host; } static void sl_bind_host_subcompositor(struct wl_client* client, @@ -131,23 +106,20 @@ static void sl_bind_host_subcompositor(struct wl_client* client, uint32_t version, uint32_t id) { struct sl_context* ctx = (struct sl_context*)data; - struct sl_host_subcompositor* host; - - host = malloc(sizeof(*host)); - assert(host); + struct sl_host_subcompositor* host = new sl_host_subcompositor(); host->ctx = ctx; host->resource = wl_resource_create(client, &wl_subcompositor_interface, 1, id); wl_resource_set_implementation(host->resource, &sl_subcompositor_implementation, host, sl_destroy_host_subcompositor); - host->proxy = + host->proxy = static_cast( wl_registry_bind(wl_display_get_registry(ctx->display), - ctx->subcompositor->id, &wl_subcompositor_interface, 1); + ctx->subcompositor->id, &wl_subcompositor_interface, 1)); wl_subcompositor_set_user_data(host->proxy, host); } struct sl_global* sl_subcompositor_global_create(struct sl_context* ctx) { return sl_global_create(ctx, &wl_subcompositor_interface, 1, ctx, sl_bind_host_subcompositor); -} \ No newline at end of file +} diff --git a/sommelier/sommelier-text-input.c b/sommelier/sommelier-text-input.c deleted file mode 100644 index 24b334e..0000000 --- a/sommelier/sommelier-text-input.c +++ /dev/null @@ -1,332 +0,0 @@ -// Copyright 2018 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "sommelier.h" - -#include -#include -#include - -#include "text-input-unstable-v1-client-protocol.h" -#include "text-input-unstable-v1-server-protocol.h" - -struct sl_host_text_input_manager { - struct sl_context* ctx; - struct wl_resource* resource; - struct zwp_text_input_manager_v1* proxy; -}; - -struct sl_host_text_input { - struct sl_context* ctx; - struct wl_resource* resource; - struct zwp_text_input_v1* proxy; -}; - -static void sl_text_input_activate(struct wl_client* client, - struct wl_resource* resource, - struct wl_resource* seat, - struct wl_resource* surface) { - struct sl_host_text_input* host = wl_resource_get_user_data(resource); - struct sl_host_seat* host_seat = wl_resource_get_user_data(seat); - struct sl_host_surface* host_surface = wl_resource_get_user_data(surface); - - zwp_text_input_v1_activate(host->proxy, host_seat->proxy, - host_surface->proxy); -} - -static void sl_text_input_deactivate(struct wl_client* client, - struct wl_resource* resource, - struct wl_resource* seat) { - struct sl_host_text_input* host = wl_resource_get_user_data(resource); - struct sl_host_seat* host_seat = wl_resource_get_user_data(seat); - - zwp_text_input_v1_deactivate(host->proxy, host_seat->proxy); -} - -static void sl_text_input_show_input_panel(struct wl_client* client, - struct wl_resource* resource) { - struct sl_host_text_input* host = wl_resource_get_user_data(resource); - - zwp_text_input_v1_show_input_panel(host->proxy); -} - -static void sl_text_input_hide_input_panel(struct wl_client* client, - struct wl_resource* resource) { - struct sl_host_text_input* host = wl_resource_get_user_data(resource); - - zwp_text_input_v1_hide_input_panel(host->proxy); -} - -static void sl_text_input_reset(struct wl_client* client, - struct wl_resource* resource) { - struct sl_host_text_input* host = wl_resource_get_user_data(resource); - - zwp_text_input_v1_reset(host->proxy); -} - -static void sl_text_input_set_surrounding_text(struct wl_client* client, - struct wl_resource* resource, - const char* text, - uint32_t cursor, - uint32_t anchor) { - struct sl_host_text_input* host = wl_resource_get_user_data(resource); - - zwp_text_input_v1_set_surrounding_text(host->proxy, text, cursor, anchor); -} - -static void sl_text_input_set_content_type(struct wl_client* client, - struct wl_resource* resource, - uint32_t hint, - uint32_t purpose) { - struct sl_host_text_input* host = wl_resource_get_user_data(resource); - - zwp_text_input_v1_set_content_type(host->proxy, hint, purpose); -} - -static void sl_text_input_set_cursor_rectangle(struct wl_client* client, - struct wl_resource* resource, - int32_t x, - int32_t y, - int32_t width, - int32_t height) { - struct sl_host_text_input* host = wl_resource_get_user_data(resource); - - zwp_text_input_v1_set_cursor_rectangle(host->proxy, x, y, width, height); -} - -static void sl_text_input_set_preferred_language(struct wl_client* client, - struct wl_resource* resource, - const char* language) { - struct sl_host_text_input* host = wl_resource_get_user_data(resource); - - zwp_text_input_v1_set_preferred_language(host->proxy, language); -} - -static void sl_text_input_commit_state(struct wl_client* client, - struct wl_resource* resource, - uint32_t serial) { - struct sl_host_text_input* host = wl_resource_get_user_data(resource); - - zwp_text_input_v1_commit_state(host->proxy, serial); -} - -static void sl_text_input_invoke_action(struct wl_client* client, - struct wl_resource* resource, - uint32_t button, - uint32_t index) { - struct sl_host_text_input* host = wl_resource_get_user_data(resource); - - zwp_text_input_v1_invoke_action(host->proxy, button, index); -} - -static const struct zwp_text_input_v1_interface sl_text_input_implementation = { - sl_text_input_activate, - sl_text_input_deactivate, - sl_text_input_show_input_panel, - sl_text_input_hide_input_panel, - sl_text_input_reset, - sl_text_input_set_surrounding_text, - sl_text_input_set_content_type, - sl_text_input_set_cursor_rectangle, - sl_text_input_set_preferred_language, - sl_text_input_commit_state, - sl_text_input_invoke_action, -}; - -static void sl_text_input_enter(void* data, - struct zwp_text_input_v1* text_input, - struct wl_surface* surface) { - struct sl_host_text_input* host = zwp_text_input_v1_get_user_data(text_input); - struct sl_host_surface* host_surface = wl_surface_get_user_data(surface); - - zwp_text_input_v1_send_enter(host->resource, host_surface->resource); -} - -static void sl_text_input_leave(void* data, - struct zwp_text_input_v1* text_input) { - struct sl_host_text_input* host = zwp_text_input_v1_get_user_data(text_input); - - zwp_text_input_v1_send_leave(host->resource); -} - -static void sl_text_input_modifiers_map(void* data, - struct zwp_text_input_v1* text_input, - struct wl_array* map) { - struct sl_host_text_input* host = zwp_text_input_v1_get_user_data(text_input); - - zwp_text_input_v1_send_modifiers_map(host->resource, map); -} - -static void sl_text_input_input_panel_state( - void* data, struct zwp_text_input_v1* text_input, uint32_t state) { - struct sl_host_text_input* host = zwp_text_input_v1_get_user_data(text_input); - - zwp_text_input_v1_send_input_panel_state(host->resource, state); -} - -static void sl_text_input_preedit_string(void* data, - struct zwp_text_input_v1* text_input, - uint32_t serial, - const char* text, - const char* commit) { - struct sl_host_text_input* host = zwp_text_input_v1_get_user_data(text_input); - - zwp_text_input_v1_send_preedit_string(host->resource, serial, text, commit); -} - -static void sl_text_input_preedit_styling(void* data, - struct zwp_text_input_v1* text_input, - uint32_t index, - uint32_t length, - uint32_t style) { - struct sl_host_text_input* host = zwp_text_input_v1_get_user_data(text_input); - - zwp_text_input_v1_send_preedit_styling(host->resource, index, length, style); -} - -static void sl_text_input_preedit_cursor(void* data, - struct zwp_text_input_v1* text_input, - int32_t index) { - struct sl_host_text_input* host = zwp_text_input_v1_get_user_data(text_input); - - zwp_text_input_v1_send_preedit_cursor(host->resource, index); -} - -static void sl_text_input_commit_string(void* data, - struct zwp_text_input_v1* text_input, - uint32_t serial, - const char* text) { - struct sl_host_text_input* host = zwp_text_input_v1_get_user_data(text_input); - - zwp_text_input_v1_send_commit_string(host->resource, serial, text); -} - -static void sl_text_input_cursor_position(void* data, - struct zwp_text_input_v1* text_input, - int32_t index, - int32_t anchor) { - struct sl_host_text_input* host = zwp_text_input_v1_get_user_data(text_input); - - zwp_text_input_v1_send_cursor_position(host->resource, index, anchor); -} - -static void sl_text_input_delete_surrounding_text( - void* data, - struct zwp_text_input_v1* text_input, - int32_t index, - uint32_t length) { - struct sl_host_text_input* host = zwp_text_input_v1_get_user_data(text_input); - - zwp_text_input_v1_send_delete_surrounding_text(host->resource, index, length); -} - -static void sl_text_input_keysym(void* data, - struct zwp_text_input_v1* text_input, - uint32_t serial, - uint32_t time, - uint32_t sym, - uint32_t state, - uint32_t modifiers) { - struct sl_host_text_input* host = zwp_text_input_v1_get_user_data(text_input); - - zwp_text_input_v1_send_keysym(host->resource, serial, time, sym, state, - modifiers); -} - -static void sl_text_input_language(void* data, - struct zwp_text_input_v1* text_input, - uint32_t serial, - const char* language) { - struct sl_host_text_input* host = zwp_text_input_v1_get_user_data(text_input); - - zwp_text_input_v1_send_language(host->resource, serial, language); -} - -static void sl_text_input_text_direction(void* data, - struct zwp_text_input_v1* text_input, - uint32_t serial, - uint32_t direction) { - struct sl_host_text_input* host = zwp_text_input_v1_get_user_data(text_input); - - zwp_text_input_v1_send_text_direction(host->resource, serial, direction); -} - -static const struct zwp_text_input_v1_listener sl_text_input_listener = { - sl_text_input_enter, sl_text_input_leave, - sl_text_input_modifiers_map, sl_text_input_input_panel_state, - sl_text_input_preedit_string, sl_text_input_preedit_styling, - sl_text_input_preedit_cursor, sl_text_input_commit_string, - sl_text_input_cursor_position, sl_text_input_delete_surrounding_text, - sl_text_input_keysym, sl_text_input_language, - sl_text_input_text_direction, -}; - -static void sl_destroy_host_text_input(struct wl_resource* resource) { - struct sl_host_text_input* host = wl_resource_get_user_data(resource); - - zwp_text_input_v1_destroy(host->proxy); - wl_resource_set_user_data(resource, NULL); - free(host); -} - -static void sl_text_input_manager_create_text_input( - struct wl_client* client, struct wl_resource* resource, uint32_t id) { - struct sl_host_text_input_manager* host = wl_resource_get_user_data(resource); - struct wl_resource* text_input_resource = - wl_resource_create(client, &zwp_text_input_v1_interface, 1, id); - struct sl_host_text_input* text_input_host = - malloc(sizeof(struct sl_host_text_input)); - - text_input_host->resource = text_input_resource; - text_input_host->ctx = host->ctx; - text_input_host->proxy = zwp_text_input_manager_v1_create_text_input( - host->ctx->text_input_manager->internal); - wl_resource_set_implementation(text_input_resource, - &sl_text_input_implementation, text_input_host, - sl_destroy_host_text_input); - zwp_text_input_v1_set_user_data(text_input_host->proxy, text_input_host); - zwp_text_input_v1_add_listener(text_input_host->proxy, - &sl_text_input_listener, text_input_host); -} - -static void sl_destroy_host_text_input_manager(struct wl_resource* resource) { - struct sl_host_text_input_manager* host = wl_resource_get_user_data(resource); - - zwp_text_input_manager_v1_destroy(host->proxy); - wl_resource_set_user_data(resource, NULL); - free(host); -} - -static struct zwp_text_input_manager_v1_interface - sl_text_input_manager_implementation = { - sl_text_input_manager_create_text_input, -}; - -static void sl_bind_host_text_input_manager(struct wl_client* client, - void* data, - uint32_t version, - uint32_t id) { - struct sl_context* ctx = (struct sl_context*)data; - struct sl_text_input_manager* text_input_manager = ctx->text_input_manager; - struct sl_host_text_input_manager* host; - - host = malloc(sizeof(*host)); - assert(host); - host->ctx = ctx; - host->resource = - wl_resource_create(client, &zwp_text_input_manager_v1_interface, 1, id); - wl_resource_set_implementation(host->resource, - &sl_text_input_manager_implementation, host, - sl_destroy_host_text_input_manager); - host->proxy = wl_registry_bind(wl_display_get_registry(ctx->display), - text_input_manager->id, - &zwp_text_input_manager_v1_interface, - wl_resource_get_version(host->resource)); - zwp_text_input_manager_v1_set_user_data(host->proxy, host); -} - -struct sl_global* sl_text_input_manager_global_create(struct sl_context* ctx) { - return sl_global_create(ctx, &zwp_text_input_manager_v1_interface, 1, ctx, - sl_bind_host_text_input_manager); -} diff --git a/sommelier/sommelier-text-input.cc b/sommelier/sommelier-text-input.cc new file mode 100644 index 0000000..a55ded6 --- /dev/null +++ b/sommelier/sommelier-text-input.cc @@ -0,0 +1,551 @@ +// Copyright 2018 The ChromiumOS Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sommelier.h" // NOLINT(build/include_directory) +#include "sommelier-transform.h" // NOLINT(build/include_directory) +#include "weak-resource-ptr.h" // NOLINT(build/include_directory) + +#include +#include +#include +#include + +#include "text-input-extension-unstable-v1-client-protocol.h" // NOLINT(build/include_directory) +#include "text-input-extension-unstable-v1-server-protocol.h" // NOLINT(build/include_directory) +#include "text-input-unstable-v1-client-protocol.h" // NOLINT(build/include_directory) +#include "text-input-unstable-v1-server-protocol.h" // NOLINT(build/include_directory) +#include "text-input-x11-unstable-v1-server-protocol.h" // NOLINT(build/include_directory) + +namespace { + +// Versions supported by sommelier. +constexpr uint32_t kTextInputManagerVersion = 1; +constexpr uint32_t kTextInputExtensionVersion = 4; +constexpr uint32_t kTextInputX11Version = 1; + +} // namespace + +struct sl_host_text_input_manager { + struct sl_context* ctx; + struct wl_resource* resource; + struct zwp_text_input_manager_v1* proxy; +}; + +struct sl_host_text_input { + struct sl_context* ctx; + struct wl_resource* resource; + struct zwp_text_input_v1* proxy; + + WeakResourcePtr active_surface; +}; +MAP_STRUCTS(zwp_text_input_v1, sl_host_text_input); + +struct sl_host_text_input_extension { + struct sl_context* ctx; + struct wl_resource* resource; + struct zcr_text_input_extension_v1* proxy; +}; + +struct sl_host_extended_text_input { + struct sl_context* ctx; + struct wl_resource* resource; + struct sl_host_text_input* host_text_input; + struct zcr_extended_text_input_v1* proxy; +}; +MAP_STRUCTS(zcr_extended_text_input_v1, sl_host_extended_text_input); + +static void sl_text_input_activate(wl_client* client, + wl_resource* resource, + wl_resource* seat, + wl_resource* surface) { + struct sl_host_text_input* host = + static_cast(wl_resource_get_user_data(resource)); + struct sl_host_seat* host_seat = + static_cast(wl_resource_get_user_data(seat)); + struct sl_host_surface* host_surface = + static_cast(wl_resource_get_user_data(surface)); + + host->active_surface = host_surface; + zwp_text_input_v1_activate(host->proxy, host_seat->proxy, + host_surface->proxy); +} + +static void sl_text_input_deactivate(wl_client* client, + wl_resource* resource, + wl_resource* seat) { + struct sl_host_text_input* host = + static_cast(wl_resource_get_user_data(resource)); + struct sl_host_seat* host_seat = + static_cast(wl_resource_get_user_data(seat)); + + host->active_surface.Reset(); + + zwp_text_input_v1_deactivate(host->proxy, host_seat->proxy); +} + +static void sl_text_input_set_cursor_rectangle(wl_client* client, + wl_resource* resource, + int32_t x, + int32_t y, + int32_t width, + int32_t height) { + struct sl_host_text_input* host = + static_cast(wl_resource_get_user_data(resource)); + + if (!host->active_surface) + return; + + int32_t x1 = x; + int32_t y1 = y; + int32_t x2 = x + width; + int32_t y2 = y + height; + + sl_transform_guest_to_host(host->ctx, host->active_surface.get(), &x1, &y1); + sl_transform_guest_to_host(host->ctx, host->active_surface.get(), &x2, &y2); + + zwp_text_input_v1_set_cursor_rectangle(host->proxy, x1, y1, x2 - x1, y2 - y1); +} + +static const struct zwp_text_input_v1_interface sl_text_input_implementation = { + sl_text_input_activate, + sl_text_input_deactivate, + ForwardRequest, + ForwardRequest, + ForwardRequest, + ForwardRequest, + ForwardRequest, + sl_text_input_set_cursor_rectangle, + ForwardRequest, + ForwardRequest, + ForwardRequest, +}; + +static void sl_text_input_enter(void* data, + struct zwp_text_input_v1* text_input, + struct wl_surface* surface) { + // This is not currently used by cros_im. We can't simply forward the event + // as for an x11-hosted cros_im instance the text_input and wl_surface + // objects will be on different clients. We could add a corresponding event + // to text_input_x11 if needed. +} + +static void sl_text_input_leave(void* data, + struct zwp_text_input_v1* text_input) { + struct sl_host_text_input* host = static_cast( + zwp_text_input_v1_get_user_data(text_input)); + + zwp_text_input_v1_send_leave(host->resource); +} + +static void sl_text_input_modifiers_map(void* data, + struct zwp_text_input_v1* text_input, + struct wl_array* map) { + struct sl_host_text_input* host = static_cast( + zwp_text_input_v1_get_user_data(text_input)); + + zwp_text_input_v1_send_modifiers_map(host->resource, map); +} + +static void sl_text_input_input_panel_state( + void* data, struct zwp_text_input_v1* text_input, uint32_t state) { + struct sl_host_text_input* host = static_cast( + zwp_text_input_v1_get_user_data(text_input)); + + zwp_text_input_v1_send_input_panel_state(host->resource, state); +} + +static void sl_text_input_preedit_string(void* data, + struct zwp_text_input_v1* text_input, + uint32_t serial, + const char* text, + const char* commit) { + struct sl_host_text_input* host = static_cast( + zwp_text_input_v1_get_user_data(text_input)); + + zwp_text_input_v1_send_preedit_string(host->resource, serial, text, commit); +} + +static void sl_text_input_preedit_styling(void* data, + struct zwp_text_input_v1* text_input, + uint32_t index, + uint32_t length, + uint32_t style) { + struct sl_host_text_input* host = static_cast( + zwp_text_input_v1_get_user_data(text_input)); + + zwp_text_input_v1_send_preedit_styling(host->resource, index, length, style); +} + +static void sl_text_input_preedit_cursor(void* data, + struct zwp_text_input_v1* text_input, + int32_t index) { + struct sl_host_text_input* host = static_cast( + zwp_text_input_v1_get_user_data(text_input)); + + zwp_text_input_v1_send_preedit_cursor(host->resource, index); +} + +static void sl_text_input_commit_string(void* data, + struct zwp_text_input_v1* text_input, + uint32_t serial, + const char* text) { + struct sl_host_text_input* host = static_cast( + zwp_text_input_v1_get_user_data(text_input)); + + zwp_text_input_v1_send_commit_string(host->resource, serial, text); +} + +static void sl_text_input_cursor_position(void* data, + struct zwp_text_input_v1* text_input, + int32_t index, + int32_t anchor) { + struct sl_host_text_input* host = static_cast( + zwp_text_input_v1_get_user_data(text_input)); + + zwp_text_input_v1_send_cursor_position(host->resource, index, anchor); +} + +static void sl_text_input_delete_surrounding_text( + void* data, + struct zwp_text_input_v1* text_input, + int32_t index, + uint32_t length) { + struct sl_host_text_input* host = static_cast( + zwp_text_input_v1_get_user_data(text_input)); + + zwp_text_input_v1_send_delete_surrounding_text(host->resource, index, length); +} + +static void sl_text_input_keysym(void* data, + struct zwp_text_input_v1* text_input, + uint32_t serial, + uint32_t time, + uint32_t sym, + uint32_t state, + uint32_t modifiers) { + struct sl_host_text_input* host = static_cast( + zwp_text_input_v1_get_user_data(text_input)); + + zwp_text_input_v1_send_keysym(host->resource, serial, time, sym, state, + modifiers); +} + +static void sl_text_input_language(void* data, + struct zwp_text_input_v1* text_input, + uint32_t serial, + const char* language) { + struct sl_host_text_input* host = static_cast( + zwp_text_input_v1_get_user_data(text_input)); + + zwp_text_input_v1_send_language(host->resource, serial, language); +} + +static void sl_text_input_text_direction(void* data, + struct zwp_text_input_v1* text_input, + uint32_t serial, + uint32_t direction) { + struct sl_host_text_input* host = static_cast( + zwp_text_input_v1_get_user_data(text_input)); + + zwp_text_input_v1_send_text_direction(host->resource, serial, direction); +} + +static const struct zwp_text_input_v1_listener sl_text_input_listener = { + sl_text_input_enter, sl_text_input_leave, + sl_text_input_modifiers_map, sl_text_input_input_panel_state, + sl_text_input_preedit_string, sl_text_input_preedit_styling, + sl_text_input_preedit_cursor, sl_text_input_commit_string, + sl_text_input_cursor_position, sl_text_input_delete_surrounding_text, + sl_text_input_keysym, sl_text_input_language, + sl_text_input_text_direction, +}; + +static void sl_destroy_host_text_input(struct wl_resource* resource) { + struct sl_host_text_input* host = + static_cast(wl_resource_get_user_data(resource)); + + zwp_text_input_v1_destroy(host->proxy); + wl_resource_set_user_data(resource, NULL); + delete host; +} + +static void sl_text_input_manager_create_text_input( + struct wl_client* client, struct wl_resource* resource, uint32_t id) { + struct sl_host_text_input_manager* host = + static_cast( + wl_resource_get_user_data(resource)); + struct wl_resource* text_input_resource = + wl_resource_create(client, &zwp_text_input_v1_interface, + wl_resource_get_version(resource), id); + struct sl_host_text_input* text_input_host = new sl_host_text_input(); + + text_input_host->resource = text_input_resource; + text_input_host->ctx = host->ctx; + text_input_host->proxy = + zwp_text_input_manager_v1_create_text_input(host->proxy); + wl_resource_set_implementation(text_input_resource, + &sl_text_input_implementation, text_input_host, + sl_destroy_host_text_input); + zwp_text_input_v1_add_listener(text_input_host->proxy, + &sl_text_input_listener, text_input_host); +} + +static void sl_destroy_host_text_input_manager(struct wl_resource* resource) { + struct sl_host_text_input_manager* host = + static_cast( + wl_resource_get_user_data(resource)); + + zwp_text_input_manager_v1_destroy(host->proxy); + wl_resource_set_user_data(resource, NULL); + delete host; +} + +static struct zwp_text_input_manager_v1_interface + sl_text_input_manager_implementation = { + sl_text_input_manager_create_text_input, +}; + +static void sl_bind_host_text_input_manager(struct wl_client* client, + void* data, + uint32_t version, + uint32_t id) { + struct sl_context* ctx = (struct sl_context*)data; + struct sl_text_input_manager* text_input_manager = ctx->text_input_manager; + struct sl_host_text_input_manager* host = new sl_host_text_input_manager(); + host->ctx = ctx; + host->resource = + wl_resource_create(client, &zwp_text_input_manager_v1_interface, + std::min(version, kTextInputManagerVersion), id); + wl_resource_set_implementation(host->resource, + &sl_text_input_manager_implementation, host, + sl_destroy_host_text_input_manager); + host->proxy = static_cast(wl_registry_bind( + wl_display_get_registry(ctx->display), text_input_manager->id, + &zwp_text_input_manager_v1_interface, + wl_resource_get_version(host->resource))); + zwp_text_input_manager_v1_set_user_data(host->proxy, host); +} + +struct sl_global* sl_text_input_manager_global_create(struct sl_context* ctx) { + return sl_global_create(ctx, &zwp_text_input_manager_v1_interface, + kTextInputManagerVersion, ctx, + sl_bind_host_text_input_manager); +} + +static void sl_extended_text_input_destroy(struct wl_client* client, + struct wl_resource* resource) { + wl_resource_destroy(resource); +} + +static const struct zcr_extended_text_input_v1_interface + sl_extended_text_input_implementation = { + sl_extended_text_input_destroy, + ForwardRequest, + ForwardRequest< + zcr_extended_text_input_v1_set_grammar_fragment_at_cursor>, + ForwardRequest, +}; + +static void sl_extended_text_input_set_preedit_region( + void* data, + struct zcr_extended_text_input_v1* extended_text_input, + int32_t index, + uint32_t length) { + struct sl_host_extended_text_input* host = + static_cast( + zcr_extended_text_input_v1_get_user_data(extended_text_input)); + + zcr_extended_text_input_v1_send_set_preedit_region(host->resource, index, + length); +} + +static void sl_extended_text_input_clear_grammar_fragments( + void* data, + struct zcr_extended_text_input_v1* extended_text_input, + uint32_t start, + uint32_t end) { + struct sl_host_extended_text_input* host = + static_cast( + zcr_extended_text_input_v1_get_user_data(extended_text_input)); + + zcr_extended_text_input_v1_send_clear_grammar_fragments(host->resource, start, + end); +} + +static void sl_extended_text_input_add_grammar_fragment( + void* data, + struct zcr_extended_text_input_v1* extended_text_input, + uint32_t start, + uint32_t end, + const char* suggestion) { + struct sl_host_extended_text_input* host = + static_cast( + zcr_extended_text_input_v1_get_user_data(extended_text_input)); + + zcr_extended_text_input_v1_send_add_grammar_fragment(host->resource, start, + end, suggestion); +} + +static void sl_extended_text_input_set_autocorrect_range( + void* data, + struct zcr_extended_text_input_v1* extended_text_input, + uint32_t start, + uint32_t end) { + struct sl_host_extended_text_input* host = + static_cast( + zcr_extended_text_input_v1_get_user_data(extended_text_input)); + + zcr_extended_text_input_v1_send_set_autocorrect_range(host->resource, start, + end); +} + +static const struct zcr_extended_text_input_v1_listener + sl_extended_text_input_listener = { + sl_extended_text_input_set_preedit_region, + sl_extended_text_input_clear_grammar_fragments, + sl_extended_text_input_add_grammar_fragment, + sl_extended_text_input_set_autocorrect_range, +}; + +static void sl_destroy_host_extended_text_input(struct wl_resource* resource) { + struct sl_host_extended_text_input* host = + static_cast( + wl_resource_get_user_data(resource)); + + zcr_extended_text_input_v1_destroy(host->proxy); + wl_resource_set_user_data(resource, NULL); + delete host; +} + +static void sl_text_input_extension_get_extended_text_input( + struct wl_client* client, + struct wl_resource* resource, + uint32_t id, + struct wl_resource* text_input) { + struct sl_host_text_input_extension* host = + static_cast( + wl_resource_get_user_data(resource)); + struct sl_host_text_input* host_text_input = + static_cast(wl_resource_get_user_data(text_input)); + struct wl_resource* extended_text_input_resource = + wl_resource_create(client, &zcr_extended_text_input_v1_interface, + wl_resource_get_version(resource), id); + struct sl_host_extended_text_input* extended_text_input_host = + new sl_host_extended_text_input(); + + extended_text_input_host->resource = extended_text_input_resource; + extended_text_input_host->ctx = host->ctx; + extended_text_input_host->proxy = + zcr_text_input_extension_v1_get_extended_text_input( + host->proxy, host_text_input->proxy); + wl_resource_set_implementation( + extended_text_input_resource, &sl_extended_text_input_implementation, + extended_text_input_host, sl_destroy_host_extended_text_input); + zcr_extended_text_input_v1_add_listener(extended_text_input_host->proxy, + &sl_extended_text_input_listener, + extended_text_input_host); +} // NOLINT(whitespace/indent) + +static void sl_destroy_host_text_input_extension(struct wl_resource* resource) { + struct sl_host_text_input_extension* host = + static_cast( + wl_resource_get_user_data(resource)); + + zcr_text_input_extension_v1_destroy(host->proxy); + wl_resource_set_user_data(resource, NULL); + delete host; +} + +static struct zcr_text_input_extension_v1_interface + sl_text_input_extension_implementation = { + sl_text_input_extension_get_extended_text_input, +}; + +static void sl_bind_host_text_input_extension(struct wl_client* client, + void* data, + uint32_t version, + uint32_t id) { + struct sl_context* ctx = (struct sl_context*)data; + struct sl_text_input_extension* text_input_extension = + ctx->text_input_extension; + struct sl_host_text_input_extension* host = + new sl_host_text_input_extension(); + host->ctx = ctx; + host->resource = + wl_resource_create(client, &zcr_text_input_extension_v1_interface, + std::min(version, kTextInputExtensionVersion), id); + wl_resource_set_implementation(host->resource, + &sl_text_input_extension_implementation, host, + sl_destroy_host_text_input_extension); + host->proxy = static_cast(wl_registry_bind( + wl_display_get_registry(ctx->display), text_input_extension->id, + &zcr_text_input_extension_v1_interface, + wl_resource_get_version(host->resource))); + zcr_text_input_extension_v1_set_user_data(host->proxy, host); +} + +struct sl_global* sl_text_input_extension_global_create( + struct sl_context* ctx) { + return sl_global_create(ctx, &zcr_text_input_extension_v1_interface, + kTextInputExtensionVersion, ctx, + sl_bind_host_text_input_extension); +} + +static void sl_text_input_x11_activate(wl_client* client, + wl_resource* resource, + wl_resource* text_input, + wl_resource* seat, + uint32_t x11_window_id) { + struct sl_host_text_input* host_text_input = + static_cast(wl_resource_get_user_data(text_input)); + struct sl_host_seat* host_seat = + static_cast(wl_resource_get_user_data(seat)); + assert(host_text_input); + assert(host_seat); + + struct sl_context* ctx = host_text_input->ctx; + + struct sl_window* window; + wl_list_for_each(window, &ctx->windows, link) { + if (window->id != x11_window_id) + continue; + if (!window->host_surface_id) + return; + struct wl_resource* host_window_resource = + wl_client_get_object(ctx->client, window->host_surface_id); + if (!host_window_resource) + return; + sl_host_surface* host_surface = static_cast( + wl_resource_get_user_data(host_window_resource)); + host_text_input->active_surface = host_surface; + zwp_text_input_v1_activate(host_text_input->proxy, host_seat->proxy, + host_surface->proxy); + return; + } +} + +static const struct zcr_text_input_x11_v1_interface + sl_text_input_x11_implementation = { + sl_text_input_x11_activate, +}; + +static void sl_bind_host_text_input_x11(struct wl_client* client, + void* data, + uint32_t version, + uint32_t id) { + // This exists only between sommelier and its clients and there is no proxy + // to the host. For simplicity we don't use a sl_host_text_input_x11 + // type as it is not needed. + wl_resource* resource = + wl_resource_create(client, &zcr_text_input_x11_v1_interface, + std::min(version, kTextInputX11Version), id); + wl_resource_set_implementation(resource, &sl_text_input_x11_implementation, + nullptr, nullptr); +} + +struct sl_global* sl_text_input_x11_global_create(struct sl_context* ctx) { + return sl_global_create(ctx, &zcr_text_input_x11_v1_interface, + kTextInputX11Version, ctx, + sl_bind_host_text_input_x11); +} diff --git a/sommelier/sommelier-timing.cc b/sommelier/sommelier-timing.cc new file mode 100644 index 0000000..ad3e3bb --- /dev/null +++ b/sommelier/sommelier-timing.cc @@ -0,0 +1,102 @@ +// Copyright 2021 The ChromiumOS Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sommelier-timing.h" // NOLINT(build/include_directory) + +#include +#include +#include +#include +#include + +#define NSEC_PER_SEC 1000000000 +#define NSEC_PER_USEC 1000 + +static inline int64_t timespec_to_ns(timespec* t) { + return (int64_t)t->tv_sec * NSEC_PER_SEC + t->tv_nsec; +} + +// Records start time to calculate first delta. +void Timing::RecordStartTime() { + clock_gettime(CLOCK_REALTIME, &last_event); +} + +int64_t Timing::GetTime() { + timespec tp; + clock_gettime(CLOCK_REALTIME, &tp); + int64_t now = timespec_to_ns(&tp); + int64_t last = timespec_to_ns(&last_event); + last_event = tp; + return now - last; +} + +// Create a new action, add info gained from attach call. +void Timing::UpdateLastAttach(int surface_id, int buffer_id) { + actions[event_id % kMaxNumActions] = + BufferAction(GetTime(), surface_id, buffer_id, BufferAction::ATTACH); + event_id++; +} + +// Create a new action, add info gained from commit call. +void Timing::UpdateLastCommit(int surface_id) { + actions[event_id % kMaxNumActions] = BufferAction( + GetTime(), surface_id, kUnknownBufferId, BufferAction::COMMIT); + event_id++; +} + +// Add a release action with release timing info. +void Timing::UpdateLastRelease(int buffer_id) { + actions[event_id % kMaxNumActions] = BufferAction( + GetTime(), kUnknownSurfaceId, buffer_id, BufferAction::RELEASE); + event_id++; +} + +// Output the recorded actions to the timing log file. +void Timing::OutputLog() { + if (event_id == 0) { + std::cout << "No events in buffer, exiting" << std::endl; + return; + } + + std::cout << "Writing buffer activity to the timing log file" << std::endl; + + std::string output_filename = + std::string(filename) + "_set_" + std::to_string(saves); + + std::ofstream outfile(output_filename); + + int start = 0; + int buf_size = event_id; + if (event_id >= kMaxNumActions) { + start = event_id % kMaxNumActions; + buf_size = kMaxNumActions; + } + + outfile << "Type Surface_ID Buffer_ID Delta_Time" << std::endl; + for (int i = 0; i < buf_size; i++) { + int idx = (i + start) % kMaxNumActions; + std::string type("?"); + if (actions[idx].action_type == BufferAction::ATTACH) { + type = "a"; + } else if (actions[idx].action_type == BufferAction::COMMIT) { + type = "c"; + } else if (actions[idx].action_type == BufferAction::RELEASE) { + type = "r"; + } + outfile << type << " "; + outfile << actions[idx].surface_id << " "; + outfile << actions[idx].buffer_id << " "; + outfile << static_cast(actions[idx].delta_time) / NSEC_PER_USEC + << std::endl; + } + + std::stringstream nsec; + nsec << std::setw(9) << std::setfill('0') << last_event.tv_nsec; + outfile << "EndTime " << event_id - 1 << " " << last_event.tv_sec << "." + << nsec.str() << std::endl; + + outfile.close(); + std::cout << "Finished writing " << output_filename << std::endl; + ++saves; +} diff --git a/sommelier/sommelier-timing.h b/sommelier/sommelier-timing.h new file mode 100644 index 0000000..37b9e76 --- /dev/null +++ b/sommelier/sommelier-timing.h @@ -0,0 +1,52 @@ +// Copyright 2021 The ChromiumOS Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef VM_TOOLS_SOMMELIER_SOMMELIER_TIMING_H_ +#define VM_TOOLS_SOMMELIER_SOMMELIER_TIMING_H_ + +#include +#include + +const int kUnknownBufferId = -1; +const int kUnknownSurfaceId = -1; + +class Timing { + public: + explicit Timing(const char* fname) : filename(fname) {} + void RecordStartTime(); + void UpdateLastAttach(int surface_id, int buffer_id); + void UpdateLastCommit(int surface_id); + void UpdateLastRelease(int buffer_id); + void OutputLog(); + + private: + // 10 min * 60 sec/min * 60 frames/sec * 3 actions/frame = 108000 actions + static const int kMaxNumActions = 10 * 60 * 60 * 3; + + struct BufferAction { + enum Type { UNKNOWN, ATTACH, COMMIT, RELEASE }; + int64_t delta_time; + int surface_id; + int buffer_id; + Type action_type; + BufferAction() + : surface_id(kUnknownSurfaceId), + buffer_id(kUnknownBufferId), + action_type(UNKNOWN) {} + explicit BufferAction(int64_t dt, + int sid = kUnknownSurfaceId, + int bid = kUnknownBufferId, + Type type = UNKNOWN) + : delta_time(dt), surface_id(sid), buffer_id(bid), action_type(type) {} + }; + + BufferAction actions[kMaxNumActions]; + int event_id = 0; + int saves = 0; + const char* filename; + timespec last_event; + + int64_t GetTime(); +}; // class Timing +#endif // VM_TOOLS_SOMMELIER_SOMMELIER_TIMING_H_ diff --git a/sommelier/sommelier-tracing.cc b/sommelier/sommelier-tracing.cc new file mode 100644 index 0000000..faf9fda --- /dev/null +++ b/sommelier/sommelier-tracing.cc @@ -0,0 +1,484 @@ +// Copyright 2020 The ChromiumOS Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sommelier-tracing.h" // NOLINT(build/include_directory) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sommelier.h" // NOLINT(build/include_directory) +#include "sommelier-ctx.h" // NOLINT(build/include_directory) + +#if defined(_M_IA64) || defined(_M_IX86) || defined(__ia64__) || \ + defined(__i386__) || defined(__amd64__) || defined(__x86_64__) || \ + defined(_M_AMD64) +#define HAS_RDTSC +#ifdef _MSC_VER +#include +#else +#include +#endif +#endif +#if defined(_M_ARM) || defined(_M_ARMT) || defined(__arm__) || \ + defined(__thumb__) || defined(__aarch64__) +// TODO(jbates): support ARM CPU counter +#endif + +#if defined(PERFETTO_TRACING) +PERFETTO_TRACK_EVENT_STATIC_STORAGE(); + +std::unique_ptr tracing_session; + +void initialize_tracing(bool in_process_backend, bool system_backend) { + perfetto::TracingInitArgs args; + if (in_process_backend) { + args.backends |= perfetto::kInProcessBackend; + } + if (system_backend) { + args.backends |= perfetto::kSystemBackend; + } + + perfetto::Tracing::Initialize(args); + perfetto::TrackEvent::Register(); +} + +void enable_tracing(bool create_session) { + perfetto::TraceConfig cfg; + cfg.add_buffers()->set_size_kb(1024); // Record up to 1 MiB. + auto* ds_cfg = cfg.add_data_sources()->mutable_config(); + ds_cfg->set_name("track_event"); + + if (create_session) { + tracing_session = perfetto::Tracing::NewTrace(); + tracing_session->Setup(cfg); + tracing_session->StartBlocking(); + } +} + +void dump_trace(const char* trace_filename) { + if (!trace_filename || !*trace_filename || !tracing_session) { + return; + } + + std::vector trace_data(tracing_session->ReadTraceBlocking()); + + // Write the trace into a file. + int fd = open(trace_filename, O_RDWR | O_CREAT | O_TRUNC, 0644); + if (fd == -1) { + fprintf(stderr, "error: unable to open trace file %s: %s\n", trace_filename, + strerror(errno)); + return; + } + size_t pos = 0; + do { + ssize_t ret = write(fd, &trace_data[pos], trace_data.size() - pos); + if (ret < 0) { + if (errno == EINTR || errno == EAGAIN) { + continue; + } + fprintf(stderr, "error: unable to write trace file %s: %s\n", + trace_filename, strerror(errno)); + close(fd); + return; + } + pos += ret; + } while (pos < trace_data.size()); + close(fd); +} + +// Returns NULL if atom is not recognized. +static const char* xcb_atom_to_string(uint32_t atom) { + switch (atom) { + case XCB_ATOM_NONE: + return "XCB_ATOM_NONE"; + case XCB_ATOM_PRIMARY: + return "XCB_ATOM_PRIMARY"; + case XCB_ATOM_SECONDARY: + return "XCB_ATOM_SECONDARY"; + case XCB_ATOM_ARC: + return "XCB_ATOM_ARC"; + case XCB_ATOM_ATOM: + return "XCB_ATOM_ATOM"; + case XCB_ATOM_BITMAP: + return "XCB_ATOM_BITMAP"; + case XCB_ATOM_CARDINAL: + return "XCB_ATOM_CARDINAL"; + case XCB_ATOM_COLORMAP: + return "XCB_ATOM_COLORMAP"; + case XCB_ATOM_CURSOR: + return "XCB_ATOM_CURSOR"; + case XCB_ATOM_CUT_BUFFER0: + return "XCB_ATOM_CUT_BUFFER0"; + case XCB_ATOM_CUT_BUFFER1: + return "XCB_ATOM_CUT_BUFFER1"; + case XCB_ATOM_CUT_BUFFER2: + return "XCB_ATOM_CUT_BUFFER2"; + case XCB_ATOM_CUT_BUFFER3: + return "XCB_ATOM_CUT_BUFFER3"; + case XCB_ATOM_CUT_BUFFER4: + return "XCB_ATOM_CUT_BUFFER4"; + case XCB_ATOM_CUT_BUFFER5: + return "XCB_ATOM_CUT_BUFFER5"; + case XCB_ATOM_CUT_BUFFER6: + return "XCB_ATOM_CUT_BUFFER6"; + case XCB_ATOM_CUT_BUFFER7: + return "XCB_ATOM_CUT_BUFFER7"; + case XCB_ATOM_DRAWABLE: + return "XCB_ATOM_DRAWABLE"; + case XCB_ATOM_FONT: + return "XCB_ATOM_FONT"; + case XCB_ATOM_INTEGER: + return "XCB_ATOM_INTEGER"; + case XCB_ATOM_PIXMAP: + return "XCB_ATOM_PIXMAP"; + case XCB_ATOM_POINT: + return "XCB_ATOM_POINT"; + case XCB_ATOM_RECTANGLE: + return "XCB_ATOM_RECTANGLE"; + case XCB_ATOM_RESOURCE_MANAGER: + return "XCB_ATOM_RESOURCE_MANAGER"; + case XCB_ATOM_RGB_COLOR_MAP: + return "XCB_ATOM_RGB_COLOR_MAP"; + case XCB_ATOM_RGB_BEST_MAP: + return "XCB_ATOM_RGB_BEST_MAP"; + case XCB_ATOM_RGB_BLUE_MAP: + return "XCB_ATOM_RGB_BLUE_MAP"; + case XCB_ATOM_RGB_DEFAULT_MAP: + return "XCB_ATOM_RGB_DEFAULT_MAP"; + case XCB_ATOM_RGB_GRAY_MAP: + return "XCB_ATOM_RGB_GRAY_MAP"; + case XCB_ATOM_RGB_GREEN_MAP: + return "XCB_ATOM_RGB_GREEN_MAP"; + case XCB_ATOM_RGB_RED_MAP: + return "XCB_ATOM_RGB_RED_MAP"; + case XCB_ATOM_STRING: + return "XCB_ATOM_STRING"; + case XCB_ATOM_VISUALID: + return "XCB_ATOM_VISUALID"; + case XCB_ATOM_WINDOW: + return "XCB_ATOM_WINDOW"; + case XCB_ATOM_WM_COMMAND: + return "XCB_ATOM_WM_COMMAND"; + case XCB_ATOM_WM_HINTS: + return "XCB_ATOM_WM_HINTS"; + case XCB_ATOM_WM_CLIENT_MACHINE: + return "XCB_ATOM_WM_CLIENT_MACHINE"; + case XCB_ATOM_WM_ICON_NAME: + return "XCB_ATOM_WM_ICON_NAME"; + case XCB_ATOM_WM_ICON_SIZE: + return "XCB_ATOM_WM_ICON_SIZE"; + case XCB_ATOM_WM_NAME: + return "XCB_ATOM_WM_NAME"; + case XCB_ATOM_WM_NORMAL_HINTS: + return "XCB_ATOM_WM_NORMAL_HINTS"; + case XCB_ATOM_WM_SIZE_HINTS: + return "XCB_ATOM_WM_SIZE_HINTS"; + case XCB_ATOM_WM_ZOOM_HINTS: + return "XCB_ATOM_WM_ZOOM_HINTS"; + case XCB_ATOM_MIN_SPACE: + return "XCB_ATOM_MIN_SPACE"; + case XCB_ATOM_NORM_SPACE: + return "XCB_ATOM_NORM_SPACE"; + case XCB_ATOM_MAX_SPACE: + return "XCB_ATOM_MAX_SPACE"; + case XCB_ATOM_END_SPACE: + return "XCB_ATOM_END_SPACE"; + case XCB_ATOM_SUPERSCRIPT_X: + return "XCB_ATOM_SUPERSCRIPT_X"; + case XCB_ATOM_SUPERSCRIPT_Y: + return "XCB_ATOM_SUPERSCRIPT_Y"; + case XCB_ATOM_SUBSCRIPT_X: + return "XCB_ATOM_SUBSCRIPT_X"; + case XCB_ATOM_SUBSCRIPT_Y: + return "XCB_ATOM_SUBSCRIPT_Y"; + case XCB_ATOM_UNDERLINE_POSITION: + return "XCB_ATOM_UNDERLINE_POSITION"; + case XCB_ATOM_UNDERLINE_THICKNESS: + return "XCB_ATOM_UNDERLINE_THICKNESS"; + case XCB_ATOM_STRIKEOUT_ASCENT: + return "XCB_ATOM_STRIKEOUT_ASCENT"; + case XCB_ATOM_STRIKEOUT_DESCENT: + return "XCB_ATOM_STRIKEOUT_DESCENT"; + case XCB_ATOM_ITALIC_ANGLE: + return "XCB_ATOM_ITALIC_ANGLE"; + case XCB_ATOM_X_HEIGHT: + return "XCB_ATOM_X_HEIGHT"; + case XCB_ATOM_QUAD_WIDTH: + return "XCB_ATOM_QUAD_WIDTH"; + case XCB_ATOM_WEIGHT: + return "XCB_ATOM_WEIGHT"; + case XCB_ATOM_POINT_SIZE: + return "XCB_ATOM_POINT_SIZE"; + case XCB_ATOM_RESOLUTION: + return "XCB_ATOM_RESOLUTION"; + case XCB_ATOM_COPYRIGHT: + return "XCB_ATOM_COPYRIGHT"; + case XCB_ATOM_NOTICE: + return "XCB_ATOM_NOTICE"; + case XCB_ATOM_FONT_NAME: + return "XCB_ATOM_FONT_NAME"; + case XCB_ATOM_FAMILY_NAME: + return "XCB_ATOM_FAMILY_NAME"; + case XCB_ATOM_FULL_NAME: + return "XCB_ATOM_FULL_NAME"; + case XCB_ATOM_CAP_HEIGHT: + return "XCB_ATOM_CAP_HEIGHT"; + case XCB_ATOM_WM_CLASS: + return "XCB_ATOM_WM_CLASS"; + case XCB_ATOM_WM_TRANSIENT_FOR: + return "XCB_ATOM_WM_TRANSIENT_FOR"; + default: + return NULL; + } +} + +// Annotate with the string representation of an atom. +// +// Supports well-known XCB atoms, and the fetched sl_context::atoms list. (To +// add an atom you're interested in debugging, modify |sl_context_atom_name|.) +void perfetto_annotate_atom(struct sl_context* ctx, + const perfetto::EventContext& perfetto, + const char* event_name, + xcb_atom_t atom) { + auto* dbg = perfetto.event()->add_debug_annotations(); + dbg->set_name(event_name); + + // Quickest option is to look up the XCB atoms, which have static values. + const char* atom_str = xcb_atom_to_string(atom); + if (atom_str) { + dbg->set_string_value(atom_str, strlen(atom_str)); + return; + } + + // Failing that, check if we've fetched this atom. + for (unsigned i = 0; i < ARRAY_SIZE(ctx->atoms); ++i) { + if (atom == ctx->atoms[i].value) { + const char* name = sl_context_atom_name(i); + if (name != nullptr) { + dbg->set_string_value(name, strlen(name)); + return; + } + } + } + + // If we reach here, we didn't find the atom name. + // We could ask the X server but that would require a round-trip. + std::string unknown("set_string_value(unknown); +} + +void perfetto_annotate_xcb_property_state(const perfetto::EventContext& event, + const char* name, + unsigned int state) { + auto* dbg = event.event()->add_debug_annotations(); + dbg->set_name(name); + if (state == XCB_PROPERTY_NEW_VALUE) { + static const std::string prop_new("XCB_PROPERTY_NEW_VALUE"); + dbg->set_string_value(prop_new); + } else if (state == XCB_PROPERTY_DELETE) { + static const std::string prop_delete("XCB_PROPERTY_DELETE"); + dbg->set_string_value(prop_delete); + } else { + static const std::string unknown(""); + dbg->set_string_value(unknown); + } +} + +// Annotate the given Perfetto EventContext with the name (if known) and the ID +// of the given window. +// +// Slow (iterates a linked list); only intended to be called if tracing is +// enabled. +void perfetto_annotate_window(struct sl_context* ctx, + const perfetto::EventContext& perfetto, + const char* event_name, + xcb_window_t window_id) { + auto* dbg = perfetto.event()->add_debug_annotations(); + dbg->set_name(event_name); + struct sl_window* window = sl_lookup_window(ctx, window_id); + std::ostringstream value; + if (window != NULL && window->name != NULL) { + value << window->name << " '; + dbg->set_string_value(value.str()); +} + +void perfetto_annotate_size_hints(const perfetto::EventContext& perfetto, + const sl_wm_size_hints& size_hints) { + auto* dbg = perfetto.event()->add_debug_annotations(); + dbg->set_name("size_hints.flags"); + std::string flags; + if (size_hints.flags & US_POSITION) + flags += "US_POSITION|"; + if (size_hints.flags & US_SIZE) + flags += "US_SIZE|"; + if (size_hints.flags & P_POSITION) + flags += "P_POSITION|"; + if (size_hints.flags & P_SIZE) + flags += "P_SIZE|"; + if (size_hints.flags & P_MIN_SIZE) + flags += "P_MIN_SIZE|"; + if (size_hints.flags & P_MAX_SIZE) + flags += "P_MAX_SIZE|"; + if (size_hints.flags & P_RESIZE_INC) + flags += "P_RESIZE_INC|"; + if (size_hints.flags & P_ASPECT) + flags += "P_ASPECT|"; + if (size_hints.flags & P_BASE_SIZE) + flags += "P_BASE_SIZE|"; + if (size_hints.flags & P_WIN_GRAVITY) + flags += "P_WIN_GRAVITY|"; + if (!flags.empty()) + flags.pop_back(); // remove trailing '|' + dbg->set_string_value(flags); + + dbg = perfetto.event()->add_debug_annotations(); + dbg->set_name("size_hints.x"); + dbg->set_int_value(size_hints.x); + dbg = perfetto.event()->add_debug_annotations(); + dbg->set_name("size_hints.y"); + dbg->set_int_value(size_hints.y); + dbg = perfetto.event()->add_debug_annotations(); + dbg->set_name("size_hints.width"); + dbg->set_int_value(size_hints.width); + dbg = perfetto.event()->add_debug_annotations(); + dbg->set_name("size_hints.height"); + dbg->set_int_value(size_hints.height); + dbg = perfetto.event()->add_debug_annotations(); + dbg->set_name("size_hints.min_width"); + dbg->set_int_value(size_hints.min_width); + dbg = perfetto.event()->add_debug_annotations(); + dbg->set_name("size_hints.min_height"); + dbg->set_int_value(size_hints.min_height); + dbg = perfetto.event()->add_debug_annotations(); + dbg->set_name("size_hints.max_width"); + dbg->set_int_value(size_hints.max_width); + dbg = perfetto.event()->add_debug_annotations(); + dbg->set_name("size_hints.max_height"); + dbg->set_int_value(size_hints.max_height); + dbg = perfetto.event()->add_debug_annotations(); + dbg->set_name("size_hints.width_inc"); + dbg->set_int_value(size_hints.width_inc); + dbg = perfetto.event()->add_debug_annotations(); + dbg->set_name("size_hints.height_inc"); + dbg->set_int_value(size_hints.height_inc); + dbg = perfetto.event()->add_debug_annotations(); + dbg->set_name("size_hints.min_aspect.x"); + dbg->set_int_value(size_hints.min_aspect.x); + dbg = perfetto.event()->add_debug_annotations(); + dbg->set_name("size_hints.min_aspect.y"); + dbg->set_int_value(size_hints.min_aspect.y); + dbg = perfetto.event()->add_debug_annotations(); + dbg->set_name("size_hints.max_aspect.x"); + dbg->set_int_value(size_hints.max_aspect.x); + dbg = perfetto.event()->add_debug_annotations(); + dbg->set_name("size_hints.max_aspect.y"); + dbg->set_int_value(size_hints.max_aspect.y); + dbg = perfetto.event()->add_debug_annotations(); + dbg->set_name("size_hints.base_width"); + dbg->set_int_value(size_hints.base_width); + dbg = perfetto.event()->add_debug_annotations(); + dbg->set_name("size_hints.base_height"); + dbg->set_int_value(size_hints.base_height); + dbg = perfetto.event()->add_debug_annotations(); + dbg->set_name("size_hints.win_gravity"); + dbg->set_int_value(size_hints.win_gravity); +} + +// Add a Perfetto annotation for an X property storing a list of cardinals. +void perfetto_annotate_cardinal_list(const perfetto::EventContext& perfetto, + const char* event_name, + xcb_get_property_reply_t* reply) { + auto* dbg = perfetto.event()->add_debug_annotations(); + dbg->set_name(event_name); + + if (reply == nullptr) { + static const std::string null_str(""); + dbg->set_string_value(null_str); + return; + } + + uint32_t length = xcb_get_property_value_length(reply); + if (length % sizeof(uint32_t) != 0) { + static const std::string invalid(""); + dbg->set_string_value(invalid); + return; + } + + uint32_t* val = static_cast(xcb_get_property_value(reply)); + uint32_t items = length / sizeof(uint32_t); + if (items == 0) { + static const std::string empty(""); + dbg->set_string_value(empty); + return; + } + + std::ostringstream str; + str << '[' << val[0]; + for (uint32_t i = 1; i < items; ++i) { + str << ", " << val[i]; + } + str << ']'; + dbg->set_string_value(str.str()); +} + +static inline uint64_t get_cpu_ticks() { +#ifdef HAS_RDTSC + return __rdtsc(); +#else + return 0; +#endif +} + +static inline uint64_t get_timestamp_ns(clockid_t cid) { + struct timespec ts = {}; + clock_gettime(cid, &ts); + return static_cast(ts.tv_sec * 1000000000LL + ts.tv_nsec); +} + +void perfetto_annotate_time_sync(const perfetto::EventContext& perfetto) { + uint64_t boot_time = get_timestamp_ns(CLOCK_BOOTTIME); + uint64_t cpu_time = get_cpu_ticks(); + uint64_t monotonic_time = get_timestamp_ns(CLOCK_MONOTONIC); + // Read again to avoid cache miss overhead. + boot_time = get_timestamp_ns(CLOCK_BOOTTIME); + cpu_time = get_cpu_ticks(); + monotonic_time = get_timestamp_ns(CLOCK_MONOTONIC); + + auto* dbg = perfetto.event()->add_debug_annotations(); + dbg->set_name("clock_sync_boottime"); + dbg->set_uint_value(boot_time); + dbg = perfetto.event()->add_debug_annotations(); + dbg->set_name("clock_sync_monotonic"); + dbg->set_uint_value(monotonic_time); + dbg = perfetto.event()->add_debug_annotations(); + dbg->set_name("clock_sync_cputime"); + dbg->set_uint_value(cpu_time); +} + +#else + +// Stubs. + +void initialize_tracing(bool in_process_backend, bool system_backend) {} + +void enable_tracing(bool create_session) {} + +void dump_trace(const char* trace_filename) {} + +#endif // PERFETTO_TRACING diff --git a/sommelier/sommelier-tracing.h b/sommelier/sommelier-tracing.h new file mode 100644 index 0000000..a4bc74f --- /dev/null +++ b/sommelier/sommelier-tracing.h @@ -0,0 +1,53 @@ +// Copyright 2020 The ChromiumOS Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef VM_TOOLS_SOMMELIER_SOMMELIER_TRACING_H_ +#define VM_TOOLS_SOMMELIER_SOMMELIER_TRACING_H_ + +#if defined(PERFETTO_TRACING) +#include +#include + +PERFETTO_DEFINE_CATEGORIES( + perfetto::Category("surface").SetDescription( + "Events for Wayland surface management"), + perfetto::Category("display").SetDescription("Events for Wayland display"), + perfetto::Category("drm").SetDescription("Events for Wayland drm"), + perfetto::Category("shell").SetDescription("Events for Wayland shell"), + perfetto::Category("shm").SetDescription( + "Events for Wayland shared memory"), + perfetto::Category("viewport") + .SetDescription("Events for Wayland viewport"), + perfetto::Category("sync").SetDescription("Events for Wayland sync points"), + perfetto::Category("x11wm").SetDescription( + "Events for X11 window management"), + perfetto::Category("gaming").SetDescription("Events for Gaming"), + perfetto::Category("other").SetDescription("Uncategorized Wayland calls.")); + +void perfetto_annotate_atom(struct sl_context* ctx, + const perfetto::EventContext& perfetto, + const char* event_name, + xcb_atom_t atom); +void perfetto_annotate_xcb_property_state(const perfetto::EventContext& event, + const char* name, + uint32_t state); +void perfetto_annotate_window(struct sl_context* ctx, + const perfetto::EventContext& perfetto, + const char* event_name, + xcb_window_t window_id); +void perfetto_annotate_size_hints(const perfetto::EventContext& perfetto, + const struct sl_wm_size_hints& size_hints); +void perfetto_annotate_cardinal_list(const perfetto::EventContext& perfetto, + const char* event_name, + xcb_get_property_reply_t* reply); +void perfetto_annotate_time_sync(const perfetto::EventContext& perfetto); + +#else +#define TRACE_EVENT(category, name, ...) +#endif + +void initialize_tracing(bool in_process_backend, bool system_backend); +void enable_tracing(bool create_session); +void dump_trace(char const* filename); +#endif // VM_TOOLS_SOMMELIER_SOMMELIER_TRACING_H_ diff --git a/sommelier/sommelier-transform.cc b/sommelier/sommelier-transform.cc new file mode 100644 index 0000000..4f7ca2a --- /dev/null +++ b/sommelier/sommelier-transform.cc @@ -0,0 +1,362 @@ +// Copyright 2022 The ChromiumOS Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "sommelier-tracing.h" // NOLINT(build/include_directory) +#include "sommelier-transform.h" // NOLINT(build/include_directory) + +static void sl_transform_get_scale_factors( + struct sl_context* ctx, + const struct sl_host_surface* surface, + double* scalex, + double* scaley) { + if (ctx->use_direct_scale && surface && surface->has_own_scale) { + *scalex = surface->xdg_scale_x; + *scaley = surface->xdg_scale_y; + } else if (surface && surface->output) { + *scalex = surface->output.get()->xdg_scale_x; + *scaley = surface->output.get()->xdg_scale_y; + } else { + *scalex = ctx->xdg_scale_x; + *scaley = ctx->xdg_scale_y; + } +} + +static double sl_transform_direct_axis_scale(struct sl_context* ctx, + struct sl_host_surface* surface, + uint32_t axis) { + double scalex, scaley; + + sl_transform_get_scale_factors(ctx, surface, &scalex, &scaley); + return (axis == 0) ? scaley : scalex; +} + +static void sl_transform_direct_to_host_damage( + struct sl_context* ctx, + const struct sl_host_surface* surface, + int64_t* x, + int64_t* y, + double scale_x, + double scale_y) { + double xwhole = trunc(static_cast(*x) / scale_x); + double ywhole = trunc(static_cast(*y) / scale_y); + + *x = static_cast(xwhole); + *y = static_cast(ywhole); +} + +static void sl_transform_direct_to_guest_fixed(struct sl_context* ctx, + struct sl_host_surface* surface, + wl_fixed_t* coord, + uint32_t axis) { + double scale = sl_transform_direct_axis_scale(ctx, surface, axis); + double result = wl_fixed_to_double(*coord) * scale; + + *coord = wl_fixed_from_double(result); +} + +static void sl_transform_direct_to_guest_fixed(struct sl_context* ctx, + struct sl_host_surface* surface, + wl_fixed_t* x, + wl_fixed_t* y) { + double scalex, scaley; + + sl_transform_get_scale_factors(ctx, surface, &scalex, &scaley); + + double resultx = wl_fixed_to_double(*x) * scalex; + double resulty = wl_fixed_to_double(*y) * scaley; + + *x = wl_fixed_from_double(resultx); + *y = wl_fixed_from_double(resulty); +} + +static void sl_transform_direct_to_host_fixed(struct sl_context* ctx, + struct sl_host_surface* surface, + wl_fixed_t* coord, + uint32_t axis) { + double scale = sl_transform_direct_axis_scale(ctx, surface, axis); + double result = wl_fixed_to_double(*coord) / scale; + + *coord = wl_fixed_from_double(result); +} + +static void sl_transform_direct_to_host_fixed(struct sl_context* ctx, + struct sl_host_surface* surface, + wl_fixed_t* x, + wl_fixed_t* y) { + double scalex, scaley; + + sl_transform_get_scale_factors(ctx, surface, &scalex, &scaley); + + double resultx = wl_fixed_to_double(*x) / scalex; + double resulty = wl_fixed_to_double(*y) / scaley; + + *x = wl_fixed_from_double(resultx); + *y = wl_fixed_from_double(resulty); +} + +static void sl_transform_direct_to_guest(struct sl_context* ctx, + struct sl_host_surface* surface, + int32_t* x, + int32_t* y) { + double scalex, scaley; + + sl_transform_get_scale_factors(ctx, surface, &scalex, &scaley); + + double inputx = scalex * static_cast(*x); + double inputy = scaley * static_cast(*y); + + double xwhole = + (surface && surface->scale_round_on_x) ? lround(inputx) : trunc(inputx); + + double ywhole = + (surface && surface->scale_round_on_y) ? lround(inputy) : trunc(inputy); + + *x = static_cast(xwhole); + *y = static_cast(ywhole); +} + +static void sl_transform_direct_to_host(struct sl_context* ctx, + struct sl_host_surface* surface, + int32_t* x, + int32_t* y) { + double scalex, scaley; + + sl_transform_get_scale_factors(ctx, surface, &scalex, &scaley); + + double xwhole = trunc(static_cast(*x) / scalex); + double ywhole = trunc(static_cast(*y) / scaley); + + *x = static_cast(xwhole); + *y = static_cast(ywhole); +} + +bool sl_transform_viewport_scale(struct sl_context* ctx, + struct sl_host_surface* surface, + double contents_scale, + int32_t* width, + int32_t* height) { + double scale = ctx->scale * contents_scale; + + // TODO(mrisaacb): It may be beneficial to skip the set_destination call + // when the virtual and logical space match. + bool do_viewport = true; + + if (ctx->use_direct_scale) { + sl_transform_direct_to_host(ctx, surface, width, height); + + // For very small windows (in pixels), the resulting logical dimensions + // could be 0, which will cause issues with the viewporter interface. + // + // In these cases, fix it up here by forcing the logical output + // to be at least 1 pixel + + if (*width <= 0) + *width = 1; + + if (*height <= 0) + *height = 1; + + } else { + *width = ceil(*width / scale); + *height = ceil(*height / scale); + } + + return do_viewport; +} + +void sl_transform_damage_coord(struct sl_context* ctx, + const struct sl_host_surface* surface, + double buffer_scalex, + double buffer_scaley, + int64_t* x1, + int64_t* y1, + int64_t* x2, + int64_t* y2) { + if (ctx->use_direct_scale) { + double scalex, scaley; + + sl_transform_get_scale_factors(ctx, surface, &scalex, &scaley); + + scalex *= buffer_scalex; + scaley *= buffer_scaley; + + sl_transform_direct_to_host_damage(ctx, surface, x1, y1, scalex, scaley); + sl_transform_direct_to_host_damage(ctx, surface, x2, y2, scalex, scaley); + } else { + double sx = buffer_scalex * ctx->scale; + double sy = buffer_scaley * ctx->scale; + + // Enclosing rect after scaling and outset by one pixel to account for + // potential filtering. + *x1 = MAX(MIN_SIZE, (*x1) - 1) / sx; + *y1 = MAX(MIN_SIZE, (*y1) - 1) / sy; + *x2 = ceil(MIN((*x2) + 1, MAX_SIZE) / sx); + *y2 = ceil(MIN((*y2) + 1, MAX_SIZE) / sy); + } +} + +void sl_transform_host_to_guest(struct sl_context* ctx, + struct sl_host_surface* surface, + int32_t* x, + int32_t* y) { + if (ctx->use_direct_scale) { + sl_transform_direct_to_guest(ctx, surface, x, y); + } else { + (*x) *= ctx->scale; + (*y) *= ctx->scale; + } +} + +void sl_transform_host_to_guest_fixed(struct sl_context* ctx, + struct sl_host_surface* surface, + wl_fixed_t* x, + wl_fixed_t* y) { + if (ctx->use_direct_scale) { + sl_transform_direct_to_guest_fixed(ctx, surface, x, y); + } else { + double dx = wl_fixed_to_double(*x); + double dy = wl_fixed_to_double(*y); + + dx *= ctx->scale; + dy *= ctx->scale; + + *x = wl_fixed_from_double(dx); + *y = wl_fixed_from_double(dy); + } +} + +void sl_transform_host_to_guest_fixed(struct sl_context* ctx, + struct sl_host_surface* surface, + wl_fixed_t* coord, + uint32_t axis) { + if (ctx->use_direct_scale) { + sl_transform_direct_to_guest_fixed(ctx, surface, coord, axis); + } else { + double dx = wl_fixed_to_double(*coord); + + dx *= ctx->scale; + *coord = wl_fixed_from_double(dx); + } +} + +void sl_transform_guest_to_host(struct sl_context* ctx, + struct sl_host_surface* surface, + int32_t* x, + int32_t* y) { + if (ctx->use_direct_scale) { + sl_transform_direct_to_host(ctx, surface, x, y); + } else { + (*x) /= ctx->scale; + (*y) /= ctx->scale; + } +} + +void sl_transform_guest_to_host_fixed(struct sl_context* ctx, + struct sl_host_surface* surface, + wl_fixed_t* x, + wl_fixed_t* y) { + if (ctx->use_direct_scale) { + sl_transform_direct_to_host_fixed(ctx, surface, x, y); + } else { + double dx = wl_fixed_to_double(*x); + double dy = wl_fixed_to_double(*y); + + dx /= ctx->scale; + dy /= ctx->scale; + + *x = wl_fixed_from_double(dx); + *y = wl_fixed_from_double(dy); + } +} + +void sl_transform_guest_to_host_fixed(struct sl_context* ctx, + struct sl_host_surface* surface, + wl_fixed_t* coord, + uint32_t axis) { + if (ctx->use_direct_scale) { + sl_transform_direct_to_host_fixed(ctx, surface, coord, axis); + } else { + double dx = wl_fixed_to_double(*coord); + + dx /= ctx->scale; + *coord = wl_fixed_from_double(dx); + } +} + +void sl_transform_try_window_scale(struct sl_context* ctx, + struct sl_host_surface* surface, + int32_t width_in_pixels, + int32_t height_in_pixels) { + int32_t reverse_width = width_in_pixels; + int32_t reverse_height = height_in_pixels; + int32_t logical_width; + int32_t logical_height; + + // This function should only have an effect in direct scale mode + if (!ctx->use_direct_scale) + return; + + // Reset scale so that calls to sl_transform_get_scale_factors will not + // use the current scale. + sl_transform_reset_surface_scale(ctx, surface); + + // Transform the window dimensions using the global/per-output scaling factors + sl_transform_guest_to_host(ctx, surface, &reverse_width, &reverse_height); + + // Save the logical dimensions for later use + logical_width = reverse_width; + logical_height = reverse_height; + + // Transform the logical dimensions back to the virtual pixel dimensions + sl_transform_host_to_guest(ctx, surface, &reverse_width, &reverse_height); + + // If the computed logical width or height is zero, force the + // use of the global scaling factors + + if ((reverse_width != width_in_pixels || + reverse_height != height_in_pixels) && + (logical_width > 0 && logical_height > 0)) { + // There is no match, let's override the scaling setting on our surface + surface->has_own_scale = 1; + surface->xdg_scale_x = static_cast(width_in_pixels) / + static_cast(logical_width); + surface->xdg_scale_y = static_cast(height_in_pixels) / + static_cast(logical_height); + + surface->cached_logical_height = logical_height; + surface->cached_logical_width = logical_width; + + // Try once more to do a full cycle (pixel -> logical -> pixel), + // if we aren't equal, we need to force a round up on the translation + // to the guest. + + reverse_width = width_in_pixels; + reverse_height = height_in_pixels; + + sl_transform_guest_to_host(ctx, surface, &reverse_width, &reverse_height); + sl_transform_host_to_guest(ctx, surface, &reverse_width, &reverse_height); + + if (reverse_width != width_in_pixels) + surface->scale_round_on_x = true; + + if (reverse_height != height_in_pixels) + surface->scale_round_on_y = true; + } +} + +void sl_transform_reset_surface_scale(struct sl_context* ctx, + struct sl_host_surface* surface) { + surface->has_own_scale = 0; + surface->scale_round_on_x = surface->scale_round_on_y = false; + surface->xdg_scale_x = surface->xdg_scale_y = 0; +} + +void sl_transform_output_dimensions(struct sl_context* ctx, + int32_t* width, + int32_t* height) { + *width = (*width) * ctx->scale; + *height = (*height) * ctx->scale; +} diff --git a/sommelier/sommelier-transform.h b/sommelier/sommelier-transform.h new file mode 100644 index 0000000..b729c7b --- /dev/null +++ b/sommelier/sommelier-transform.h @@ -0,0 +1,182 @@ +// Copyright 2022 The ChromiumOS Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef VM_TOOLS_SOMMELIER_SOMMELIER_TRANSFORM_H_ +#define VM_TOOLS_SOMMELIER_SOMMELIER_TRANSFORM_H_ + +#include "sommelier.h" // NOLINT(build/include_directory) +#include "sommelier-ctx.h" // NOLINT(build/include_directory) + +// Direct Scaling Mode Explained: +// +// It will be helpful to define the 3 coordinate spaces that we need to +// manage: +// +// 1. Physical Coordinate Space: This refers to the actual physical dimensions +// of the devices display. Typical sizes would be 3840x2160, 1920x1080, etc. +// +// 2. Virtual Coordinate Space: This refers to the coordinate space that is +// formed by multiplying the scale factor with the physical dimensions. +// (Example: scale = 1.0, physical = 3840x2160, virtual = 3840x2160) +// (Example: scale = 0.5, physical = 3840x2160, virtual = 1920x1080) +// The scale factor will come from the "--scale" command line parameter or +// from the associated environment variable. +// +// 3. Host Logical Space: The dimensions of this space are defined +// entirely by the host. The exact dimensions are retrieved through +// the xdg_output interface. It is assumed that there is a direct, linear +// relationship between the logical space and the physical space on the +// host. As an example: +// a) A 1600x900 logical space +// b) A 3840x2160 physical space +// +// If we place a 1600x900 dimensioned object at the origin of the logical +// space, it should appear as a 3840x2160 object within the physical space +// (also at the origin). +// +// The product of the desired scale factor and the physical dimensions may +// result in non-integer values. In these cases, the result +// is rounded down towards zero (truncate). This slight modification +// will require recomputation of the scale factors to maintain consistency +// between the two coordinate spaces. For this reason, the (single) scale +// factor provided as input from the user is used to generate the virtual +// coordinates. Then once those have been computed (and rounded), the scale +// factors for each axis will then be recalculated using the virtual and +// logical dimensions. Each axis is given its own scale factor because +// it is possible for only one axis to require rounding. +// +// The logical coordinates come to us from the host. This is the +// coordinate space that the host is operating in. This can change +// based on the users scale settings. +// +// The physical coordinate space is no longer necessary once the virtual +// coordinate space has been formed, so no scaling factors are needed to +// convert to that space. +// +// Xwayland operates within the virtual coordinate space and the +// host is operating within its logical space. Sommelier only needs to +// facilitate translations between these two coordinate spaces. +// +// The virtual to logical scale factors are derived from the ratios between +// the virtual coordinate spaces dimensions and the logical coordinate spaces +// dimensions. +// +// In this mode, a buffer that is full screen sized within Xwayland (virtual) +// will also be full screen sized in the logical coordinate space. The same +// pattern holds with a quarter resolution sized image. With a scale factor +// of 1.0, it is expected that there will be no scaling done to present the +// image onto the screen. + +// Coordinate transform functions +// +// In general, the transformation functions fall under one of these +// two classes: +// +// 1. Transformations which follow the basic rules: +// A straight multiply for host->guest and straight divide for the opposite +// 2. Transformations which perform their transformations in a slightly +// different manner. +// +// The functions immediately following this block fall under the latter +// They are separate functions so these cases can be easily identified +// throughout the rest of the code. +// +// The functions that fall under the latter case work in the +// guest->host direction and do not have variants which work in the +// opposite direction. + +// This particular function will return true if setting a destination +// viewport size is necessary. It can be false if the host/guest spaces +// matches. +// This is a potential optimization as it removes one step +// from the guest->host surface_attach cycle. +bool sl_transform_viewport_scale(struct sl_context* ctx, + struct sl_host_surface* surface, + double contents_scale, + int32_t* width, + int32_t* height); + +void sl_transform_damage_coord(struct sl_context* ctx, + const struct sl_host_surface* surface, + double buffer_scalex, + double buffer_scaley, + int64_t* x1, + int64_t* y1, + int64_t* x2, + int64_t* y2); + +// Basic Transformations +// The following transformations fall under the basic type + +// 1D transformation functions have an axis specifier +// to indicate along which axis the transformation is to +// take place. +// +// The axis specifier will follow the wl_pointer::axis definitions +// 0 = vertical axis (Y) +// 1 = horizontal axis (X) + +void sl_transform_host_to_guest(struct sl_context* ctx, + struct sl_host_surface* surface, + int32_t* x, + int32_t* y); + +void sl_transform_host_to_guest_fixed(struct sl_context* ctx, + struct sl_host_surface* surface, + wl_fixed_t* x, + wl_fixed_t* y); + +void sl_transform_host_to_guest_fixed(struct sl_context* ctx, + struct sl_host_surface* surface, + wl_fixed_t* coord, + uint32_t axis); + +// Opposite Direction +void sl_transform_guest_to_host(struct sl_context* ctx, + struct sl_host_surface* surface, + int32_t* x, + int32_t* y); + +void sl_transform_guest_to_host_fixed(struct sl_context* ctx, + struct sl_host_surface* surface, + wl_fixed_t* x, + wl_fixed_t* y); + +void sl_transform_guest_to_host_fixed(struct sl_context* ctx, + struct sl_host_surface* surface, + wl_fixed_t* coord, + uint32_t axis); + +// Given the desired window size in virtual pixels, this function +// will see if it can be cleanly converted to logical coordinates and back. +// +// If the desired dimensions can be met with the default scaling factors, +// no intervention will take place. +// +// If the desired dimensions CANNOT be met with the default scaling factors, +// a set of scaling factors will be chosen to match the nearest logical +// coordinates to the desired virtual pixel dimensions. These scaling factors +// will then be used for all transformations being performed on this surface. +// +// This function is a no-op when not in direct scale mode. +void sl_transform_try_window_scale(struct sl_context* ctx, + struct sl_host_surface* surface, + int32_t width_in_pixels, + int32_t height_in_pixels); + +// Removes any custom scaling factors that have been set on the surface +// by try_window_scale +void sl_transform_reset_surface_scale(struct sl_context* ctx, + struct sl_host_surface* surface); + +// This function performs the physical to virtual transformation +// based on the scale factor provided by the command line/env. +// This function is called in response to the physical dimensions being sent +// by the host. The virtual dimensions are calculated by this function and +// then relayed to the guest. +void sl_transform_output_dimensions(struct sl_context* ctx, + int32_t* width, + int32_t* height); + +#endif // VM_TOOLS_SOMMELIER_SOMMELIER_TRANSFORM_H_ diff --git a/sommelier/sommelier-util.cc b/sommelier/sommelier-util.cc new file mode 100644 index 0000000..48fb3d5 --- /dev/null +++ b/sommelier/sommelier-util.cc @@ -0,0 +1,32 @@ +// Copyright 2021 The ChromiumOS Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sommelier-util.h" // NOLINT(build/include_directory) + +#include +#include + +// Performs an asprintf operation and checks the result for validity and calls +// abort() if there's a failure. Returns a newly allocated string rather than +// taking a double pointer argument like asprintf. +__attribute__((__format__(__printf__, 1, 0))) char* sl_xasprintf( + const char* fmt, ...) { + char* str; + va_list args; + va_start(args, fmt); + int rv = vasprintf(&str, fmt, args); + assert(rv >= 0); + UNUSED(rv); + va_end(args); + return str; +} + +#define DEFAULT_DELETER(TypeName, DeleteFunction) \ + namespace std { \ + void default_delete::operator()(TypeName* ptr) { \ + DeleteFunction(ptr); \ + } \ + } + +DEFAULT_DELETER(struct wl_event_source, wl_event_source_remove); diff --git a/sommelier/sommelier-util.h b/sommelier/sommelier-util.h new file mode 100644 index 0000000..ef63130 --- /dev/null +++ b/sommelier/sommelier-util.h @@ -0,0 +1,138 @@ +// Copyright 2021 The ChromiumOS Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef VM_TOOLS_SOMMELIER_SOMMELIER_UTIL_H_ +#define VM_TOOLS_SOMMELIER_SOMMELIER_UTIL_H_ + +#include +#include +#include +#include +#include + +#include + +#define errno_assert(rv) \ + { \ + int macro_private_assert_value = (rv); \ + if (!macro_private_assert_value) { \ + fprintf(stderr, "Unexpected error: %s\n", strerror(errno)); \ + assert(false); \ + } \ + } + +#define UNUSED(x) ((void)(x)) + +// Performs an asprintf operation and checks the result for validity and calls +// abort() if there's a failure. Returns a newly allocated string rather than +// taking a double pointer argument like asprintf. +__attribute__((__format__(__printf__, 1, 0))) char* sl_xasprintf( + const char* fmt, ...); + +#define DEFAULT_DELETER_FDECL(TypeName) \ + namespace std { \ + template <> \ + struct default_delete { \ + void operator()(TypeName* ptr); \ + }; \ + } + +DEFAULT_DELETER_FDECL(struct wl_event_source); + +// Maps wl_ to sl_ types, e.g. WlToSl::type == sl_host_seat. +template +struct WlToSl; + +#define MAP_STRUCTS(WlType, SlType) \ + template <> \ + struct WlToSl { \ + using type = SlType; \ + }; + +// Convert a request argument of type InArg to type OutArg. InArg is the type +// sommelier receives as a Wayland host. OutArg is the type used passed to the +// real host compositor. For Wayland resources, these will be wl_resource* and +// wl_..* (e.g. wl_surface*) respectively. +template +struct ConvertRequestArg {}; + +template <> +struct ConvertRequestArg { + inline static const char* Convert(const char* arg) { return arg; } +}; +template <> +struct ConvertRequestArg { + inline static uint32_t Convert(uint32_t arg) { return arg; } +}; +template <> +struct ConvertRequestArg { + inline static int32_t Convert(int32_t arg) { return arg; } +}; + +template +struct ConvertRequestArg { + static OutArg* Convert(wl_resource* resource) { + if (!resource) + return nullptr; + using SlType = typename WlToSl::type; + SlType* host = static_cast(wl_resource_get_user_data(resource)); + return host ? host->proxy : nullptr; + } +}; + +template +inline bool IsNullWlResource(T arg) { + return false; +} +template <> +inline bool IsNullWlResource(wl_resource* resource) { + return resource == nullptr; +} + +enum class AllowNullResource { + kNo = 0, + kYes = 1, +}; + +// Invoke the given wl_ function with each arg converted. This helper struct is +// so we can extract types from the wl_ function into a parameter pack for the +// fold expression and not require them to be explicitly written out. +template +struct ForwardRequestHelper; +template +struct ForwardRequestHelper { + template + static void Forward(struct wl_client* client, InArgs... args) { + if (allow_null == AllowNullResource::kNo) { + if ((IsNullWlResource(args) || ...)) { + fprintf(stderr, "error: received unexpected null resource in: %s\n", + __PRETTY_FUNCTION__); + return; + } + } + wl_function(ConvertRequestArg::Convert(args)...); + } +}; + +// Wraps the function which dispatches an request to the host for use as +// implementation for sommelier's implementation as a host. If null Wayland +// resources should be allowed, AllowNullResource::kYes should be set, +// otherwise the request will be considered invalid and dropped. +// Example usage: +// - ForwardRequest, +// - ForwardRequest +// +// The first argument (receiving object) is guaranteed by Wayland to be +// non-null but for code simplicity it is handled the same as the request +// arguments, with null being allowed or disallowed based on |allow_null|. +template +void ForwardRequest(InArgs... args) { + ForwardRequestHelper::template Forward( + args...); +} + +#endif // VM_TOOLS_SOMMELIER_SOMMELIER_UTIL_H_ diff --git a/sommelier/sommelier-viewporter.c b/sommelier/sommelier-viewporter.cc similarity index 76% rename from sommelier/sommelier-viewporter.c rename to sommelier/sommelier-viewporter.cc index c12a306..13659ab 100644 --- a/sommelier/sommelier-viewporter.c +++ b/sommelier/sommelier-viewporter.cc @@ -1,14 +1,15 @@ -// Copyright 2018 The Chromium OS Authors. All rights reserved. +// Copyright 2018 The ChromiumOS Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "sommelier.h" +#include "sommelier.h" // NOLINT(build/include_directory) +#include "sommelier-tracing.h" // NOLINT(build/include_directory) #include #include -#include "viewporter-client-protocol.h" -#include "viewporter-server-protocol.h" +#include "viewporter-client-protocol.h" // NOLINT(build/include_directory) +#include "viewporter-server-protocol.h" // NOLINT(build/include_directory) struct sl_host_viewporter { struct sl_viewporter* viewporter; @@ -32,7 +33,8 @@ static void sl_viewport_set_source(struct wl_client* client, wl_fixed_t y, wl_fixed_t width, wl_fixed_t height) { - struct sl_host_viewport* host = wl_resource_get_user_data(resource); + struct sl_host_viewport* host = + static_cast(wl_resource_get_user_data(resource)); host->viewport.src_x = x; host->viewport.src_y = y; @@ -44,7 +46,10 @@ static void sl_viewport_set_destination(struct wl_client* client, struct wl_resource* resource, int32_t width, int32_t height) { - struct sl_host_viewport* host = wl_resource_get_user_data(resource); + TRACE_EVENT("viewport", "sl_viewport_set_destination", "resource_id", + wl_resource_get_id(resource)); + struct sl_host_viewport* host = + static_cast(wl_resource_get_user_data(resource)); host->viewport.dst_width = width; host->viewport.dst_height = height; @@ -54,11 +59,12 @@ static const struct wp_viewport_interface sl_viewport_implementation = { sl_viewport_destroy, sl_viewport_set_source, sl_viewport_set_destination}; static void sl_destroy_host_viewport(struct wl_resource* resource) { - struct sl_host_viewport* host = wl_resource_get_user_data(resource); + struct sl_host_viewport* host = + static_cast(wl_resource_get_user_data(resource)); wl_resource_set_user_data(resource, NULL); wl_list_remove(&host->viewport.link); - free(host); + delete host; } static void sl_viewporter_destroy(struct wl_client* client, @@ -70,12 +76,9 @@ static void sl_viewporter_get_viewport(struct wl_client* client, struct wl_resource* resource, uint32_t id, struct wl_resource* surface_resource) { - struct sl_host_surface* host_surface = - wl_resource_get_user_data(surface_resource); - struct sl_host_viewport* host_viewport; - - host_viewport = malloc(sizeof(*host_viewport)); - assert(host_viewport); + struct sl_host_surface* host_surface = static_cast( + wl_resource_get_user_data(surface_resource)); + struct sl_host_viewport* host_viewport = new sl_host_viewport(); host_viewport->viewport.src_x = -1; host_viewport->viewport.src_y = -1; @@ -96,11 +99,12 @@ static const struct wp_viewporter_interface sl_viewporter_implementation = { sl_viewporter_destroy, sl_viewporter_get_viewport}; static void sl_destroy_host_viewporter(struct wl_resource* resource) { - struct sl_host_viewporter* host = wl_resource_get_user_data(resource); + struct sl_host_viewporter* host = + static_cast(wl_resource_get_user_data(resource)); wp_viewporter_destroy(host->proxy); wl_resource_set_user_data(resource, NULL); - free(host); + delete host; } static void sl_bind_host_viewporter(struct wl_client* client, @@ -108,21 +112,18 @@ static void sl_bind_host_viewporter(struct wl_client* client, uint32_t version, uint32_t id) { struct sl_context* ctx = (struct sl_context*)data; - struct sl_host_viewporter* host; - - host = malloc(sizeof(*host)); - assert(host); + struct sl_host_viewporter* host = new sl_host_viewporter(); host->viewporter = ctx->viewporter; host->resource = wl_resource_create(client, &wp_viewporter_interface, 1, id); wl_resource_set_implementation(host->resource, &sl_viewporter_implementation, host, sl_destroy_host_viewporter); - host->proxy = + host->proxy = static_cast( wl_registry_bind(wl_display_get_registry(ctx->display), - ctx->viewporter->id, &wp_viewporter_interface, 1); + ctx->viewporter->id, &wp_viewporter_interface, 1)); wp_viewporter_set_user_data(host->proxy, host); } struct sl_global* sl_viewporter_global_create(struct sl_context* ctx) { return sl_global_create(ctx, &wp_viewporter_interface, 1, ctx, sl_bind_host_viewporter); -} \ No newline at end of file +} diff --git a/sommelier/sommelier-wayland-fuzzer.cc b/sommelier/sommelier-wayland-fuzzer.cc new file mode 100644 index 0000000..f1368ce --- /dev/null +++ b/sommelier/sommelier-wayland-fuzzer.cc @@ -0,0 +1,254 @@ +// Copyright 2021 The ChromiumOS Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include +#include +#include +#include + +#include + +#include + +#include "sommelier.h" // NOLINT(build/include_directory) +#include "sommelier-ctx.h" // NOLINT(build/include_directory) +#include "virtualization/wayland_channel.h" // NOLINT(build/include_directory) + +class FuzzChannel : public WaylandChannel { + private: + int recv_fd = -1; + + public: + int send_fd = -1; + + ~FuzzChannel() { + if (send_fd != -1) { + close(send_fd); + } + if (recv_fd != -1) { + close(recv_fd); + } + } + + int32_t init() override { return 0; } + + bool supports_dmabuf() override { return false; } + + int32_t create_context(int& out_channel_fd) override { + int sv[2]; + if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sv)) { + return -errno; + } + + send_fd = sv[0]; + recv_fd = sv[1]; + out_channel_fd = sv[1]; + return 0; + } + + int32_t create_pipe(int& out_pipe_fd) override { return -1; } + + int32_t send(const struct WaylandSendReceive& send) override { return 0; } + + int32_t handle_channel_event(enum WaylandChannelEvent& event_type, + struct WaylandSendReceive& receive, + int& out_read_pipe) override { + uint8_t* buffer = static_cast(malloc(DEFAULT_BUFFER_SIZE)); + int bytes = recv(receive.channel_fd, buffer, DEFAULT_BUFFER_SIZE, 0); + if (bytes < 0) { + free(buffer); + return -errno; + } + + receive.data = buffer; + receive.data_size = bytes; + receive.num_fds = 0; + + event_type = WaylandChannelEvent::Receive; + + return 0; + } + + int32_t allocate(const struct WaylandBufferCreateInfo& create_info, + struct WaylandBufferCreateOutput& create_output) override { + return -1; + } + + int32_t sync(int dmabuf_fd, uint64_t flags) override { return 0; } + int32_t handle_pipe(int read_fd, bool readable, bool& hang_up) override { + return 0; + } + + size_t max_send_size(void) override { return DEFAULT_BUFFER_SIZE; } +}; + +void null_logger(const char*, va_list) {} + +class Environment { + public: + Environment() { + wl_log_set_handler_client(null_logger); + wl_log_set_handler_server(null_logger); + } +}; + +static int handle_host_to_sommelier_event(int fd, uint32_t mask, void* data) { + struct sl_context* ctx = (struct sl_context*)data; + int count = 0; + + if ((mask & WL_EVENT_HANGUP) || (mask & WL_EVENT_ERROR)) { + return 0; + } + + if (mask & WL_EVENT_READABLE) + count = wl_display_dispatch(ctx->display); + if (mask & WL_EVENT_WRITABLE) + wl_display_flush(ctx->display); + + if (mask == 0) { + count = wl_display_dispatch_pending(ctx->display); + wl_display_flush(ctx->display); + } + + return count; +} + +int drain_socket(int fd, uint32_t mask, void* data) { + uint8_t buffer[DEFAULT_BUFFER_SIZE]; + return recv(fd, buffer, DEFAULT_BUFFER_SIZE, 0) > 0; +} + +// Count the number of open file descriptors to make sure we don't leak any. +// Aborts on error, as this should never fail. +int count_fds() { + DIR* dir = opendir("/proc/self/fd"); + if (!dir) { + fprintf(stderr, "Failed to open /proc/self/fd: %m\n"); + abort(); + } + + // Ignore the "." and ".." entries, along with the fd we just opened. + int count = -3; + + // Needed to distinguish between eof and errors. + errno = 0; + while (struct dirent* dirent = readdir(dir)) { + count++; + } + if (errno) { + fprintf(stderr, "Failed to read from /proc/self/fd: %m\n"); + abort(); + } + + if (closedir(dir)) { + fprintf(stderr, "Failed to close /proc/self/fd: %m\n"); + abort(); + } + + return count; +} + +int LLVMFuzzerTestOneInput_real(const uint8_t* data, size_t size) { + static Environment env; + FuzzedDataProvider source(data, size); + + struct sl_context ctx; + FuzzChannel channel; + int host_to_sommelier, client_to_sommelier; + + sl_context_init_default(&ctx); + + // Create a connection from the host to sommelier. This is done via a + // WaylandChannel implementation that keeps a socketpair internally. One end + // goes to sommelier to listen on/write to, the other end is kept by us. The + // channel implements send with a no-op, so we don't ever have to read from + // our end. + ctx.host_display = wl_display_create(); + struct wl_event_loop* event_loop = + wl_display_get_event_loop(ctx.host_display); + ctx.channel = &channel; + sl_context_init_wayland_channel(&ctx, event_loop, /*display=*/false); + // `display` takes ownership of `virtwl_display_fd` + ctx.display = wl_display_connect_to_fd(ctx.virtwl_display_fd); + + // Create a connection from the client to sommelier. One end is passed into + // libwayland for sommelier to handle, the other end is owned by the + // fuzzer. We set up the event loop to drain any data send by sommelier to our + // end, and write fuzz data to our end in the main loop. + int sv[2]; + int ret = socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sv); + assert(!ret); + // wl_client takes ownership of its file descriptor + ctx.client = wl_client_create(ctx.host_display, sv[0]); + std::unique_ptr drain_event(wl_event_loop_add_fd( + event_loop, sv[1], WL_EVENT_READABLE, drain_socket, nullptr)); + + // Add the host-to-sommelier display to the event loop. The client wayland + // library doesn't have an event loop system, instead you're expected to + // integrate into another event loop using wl_display_get_fd if you need to. + std::unique_ptr display_event( + wl_event_loop_add_fd(event_loop, wl_display_get_fd(ctx.display), + WL_EVENT_READABLE | WL_EVENT_WRITABLE, + handle_host_to_sommelier_event, &ctx)); + + // Getting the registry object from the host and listening for events is the + // top-level task in sommelier. Everything else is driven by messages from the + // host or the client. + auto* registry = wl_display_get_registry(ctx.display); + wl_registry_add_listener(registry, &sl_registry_listener, &ctx); + + host_to_sommelier = channel.send_fd; + client_to_sommelier = sv[1]; + + while (source.remaining_bytes() && + !wl_list_empty(wl_display_get_client_list(ctx.host_display)) && + !wl_display_get_error(ctx.display)) { + // Randomly pick a connection to write too. + int fd = source.ConsumeBool() ? host_to_sommelier : client_to_sommelier; + + // The random length string extractor uses a terminator character to prevent + // the boundaries of a message from shifting when the fuzzer inserts or + // removed bytes. This is (probably) better then consuming a length and then + // consuming that many characters. + std::string data = source.ConsumeRandomLengthString(DEFAULT_BUFFER_SIZE); + + int sent = send(fd, data.data(), data.length(), 0); + assert(sent == data.length()); + + wl_display_flush_clients(ctx.host_display); + wl_event_loop_dispatch(event_loop, 0); + } + + wl_registry_destroy(registry); + + close(ctx.virtwl_socket_fd); + close(client_to_sommelier); + + ctx.wayland_channel_event_source.reset(); + ctx.virtwl_socket_event_source.reset(); + drain_event.reset(); + display_event.reset(); + + wl_display_destroy_clients(ctx.host_display); + wl_display_destroy(ctx.host_display); + + wl_display_disconnect(ctx.display); + + return 0; +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + int start_fds = count_fds(); + + int ret = LLVMFuzzerTestOneInput_real(data, size); + + int end_fds = count_fds(); + if (start_fds != end_fds) { + fprintf(stderr, "Leaked %d file descriptors!\n", end_fds - start_fds); + abort(); + } + + return ret; +} diff --git a/sommelier/sommelier-window.cc b/sommelier/sommelier-window.cc new file mode 100644 index 0000000..c273217 --- /dev/null +++ b/sommelier/sommelier-window.cc @@ -0,0 +1,593 @@ +// Copyright 2021 The ChromiumOS Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sommelier-window.h" // NOLINT(build/include_directory) + +#include + +#include "sommelier.h" // NOLINT(build/include_directory) +#include "sommelier-tracing.h" // NOLINT(build/include_directory) +#include "sommelier-transform.h" // NOLINT(build/include_directory) + +#include "aura-shell-client-protocol.h" // NOLINT(build/include_directory) +#include "xdg-shell-client-protocol.h" // NOLINT(build/include_directory) + +#define APPLICATION_ID_FORMAT_PREFIX "org.chromium.guest_os.%s" +#define XID_APPLICATION_ID_FORMAT APPLICATION_ID_FORMAT_PREFIX ".xid.%d" +#define WM_CLIENT_LEADER_APPLICATION_ID_FORMAT \ + APPLICATION_ID_FORMAT_PREFIX ".wmclientleader.%d" +#define WM_CLASS_APPLICATION_ID_FORMAT \ + APPLICATION_ID_FORMAT_PREFIX ".wmclass.%s" +#define X11_PROPERTY_APPLICATION_ID_FORMAT \ + APPLICATION_ID_FORMAT_PREFIX ".xprop.%s" +sl_window::sl_window(struct sl_context* ctx, + xcb_window_t id, + int x, + int y, + int width, + int height, + int border_width) + : ctx(ctx), + id(id), + x(x), + y(y), + width(width), + height(height), + border_width(border_width) { + wl_list_insert(&ctx->unpaired_windows, &link); + pixman_region32_init(&shape_rectangles); +} + +sl_window::~sl_window() { + if (this == ctx->host_focus_window) { + ctx->host_focus_window = nullptr; + ctx->needs_set_input_focus = 1; + } + + free(name); + free(clazz); + free(startup_id); + wl_list_remove(&link); + pixman_region32_fini(&shape_rectangles); +} + +void sl_configure_window(struct sl_window* window) { + TRACE_EVENT("surface", "sl_configure_window", "id", window->id); + assert(!window->pending_config.serial); + + if (window->next_config.mask) { + int values[5]; + int x = window->x; + int y = window->y; + int i = 0; + + xcb_configure_window(window->ctx->connection, window->frame_id, + window->next_config.mask, window->next_config.values); + + if (window->next_config.mask & XCB_CONFIG_WINDOW_X) + x = window->next_config.values[i++]; + if (window->next_config.mask & XCB_CONFIG_WINDOW_Y) + y = window->next_config.values[i++]; + if (window->next_config.mask & XCB_CONFIG_WINDOW_WIDTH) + window->width = window->next_config.values[i++]; + if (window->next_config.mask & XCB_CONFIG_WINDOW_HEIGHT) + window->height = window->next_config.values[i++]; + if (window->next_config.mask & XCB_CONFIG_WINDOW_BORDER_WIDTH) + window->border_width = window->next_config.values[i++]; + + // Set x/y to origin in case window gravity is not northwest as expected. + assert(window->managed); + values[0] = 0; + values[1] = 0; + values[2] = window->width; + values[3] = window->height; + values[4] = window->border_width; + xcb_configure_window( + window->ctx->connection, window->id, + XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_WIDTH | + XCB_CONFIG_WINDOW_HEIGHT | XCB_CONFIG_WINDOW_BORDER_WIDTH, + values); + + if (x != window->x || y != window->y) { + window->x = x; + window->y = y; + sl_send_configure_notify(window); + } + } + + if (window->managed) { + xcb_change_property(window->ctx->connection, XCB_PROP_MODE_REPLACE, + window->id, window->ctx->atoms[ATOM_NET_WM_STATE].value, + XCB_ATOM_ATOM, 32, window->next_config.states_length, + window->next_config.states); + } + + window->pending_config = window->next_config; + window->next_config.serial = 0; + window->next_config.mask = 0; + window->next_config.states_length = 0; +} + +void sl_send_configure_notify(struct sl_window* window) { + xcb_configure_notify_event_t event = {}; + event.response_type = XCB_CONFIGURE_NOTIFY; + event.pad0 = 0; + event.event = window->id; + event.window = window->id; + event.above_sibling = XCB_WINDOW_NONE; + event.x = static_cast(window->x); + event.y = static_cast(window->y); + event.width = static_cast(window->width); + event.height = static_cast(window->height); + event.border_width = static_cast(window->border_width); + event.override_redirect = 0; + event.pad1 = 0; + + xcb_send_event(window->ctx->connection, 0, window->id, + XCB_EVENT_MASK_STRUCTURE_NOTIFY, + reinterpret_cast(&event)); +} + +int sl_process_pending_configure_acks(struct sl_window* window, + struct sl_host_surface* host_surface) { + if (!window->pending_config.serial) + return 0; + +#ifdef COMMIT_LOOP_FIX + // Do not commit/ack if there is nothing to change. + // + // TODO(b/181077580): we should never do this, but avoiding it requires a + // more systemic fix + if (!window->pending_config.mask && window->pending_config.states_length == 0) + return 0; +#endif + + if (window->managed && host_surface) { + uint32_t width = window->width + window->border_width * 2; + uint32_t height = window->height + window->border_width * 2; + // Early out if we expect contents to match window size at some point in + // the future. + if (width != host_surface->contents_width || + height != host_surface->contents_height) { + return 0; + } + } + + if (window->xdg_surface) { + xdg_surface_ack_configure(window->xdg_surface, + window->pending_config.serial); + } + window->pending_config.serial = 0; + + if (window->next_config.serial) + sl_configure_window(window); + + return 1; +} + +void sl_commit(struct sl_window* window, struct sl_host_surface* host_surface) { + if (sl_process_pending_configure_acks(window, host_surface)) { + if (host_surface) + wl_surface_commit(host_surface->proxy); + } +} + +static void sl_internal_xdg_popup_configure(void* data, + struct xdg_popup* xdg_popup, + int32_t x, + int32_t y, + int32_t width, + int32_t height) {} + +static void sl_internal_xdg_popup_done(void* data, + struct xdg_popup* xdg_popup) {} + +static const struct xdg_popup_listener sl_internal_xdg_popup_listener = { + sl_internal_xdg_popup_configure, sl_internal_xdg_popup_done}; + +static void sl_internal_xdg_surface_configure(void* data, + struct xdg_surface* xdg_surface, + uint32_t serial) { + TRACE_EVENT("surface", "sl_internal_xdg_surface_configure"); + struct sl_window* window = + static_cast(xdg_surface_get_user_data(xdg_surface)); + + window->next_config.serial = serial; + if (!window->pending_config.serial) { + struct wl_resource* host_resource; + struct sl_host_surface* host_surface = NULL; + + host_resource = + wl_client_get_object(window->ctx->client, window->host_surface_id); + if (host_resource) + host_surface = static_cast( + wl_resource_get_user_data(host_resource)); + + sl_configure_window(window); + sl_commit(window, host_surface); + } +} + +static const struct xdg_surface_listener sl_internal_xdg_surface_listener = { + sl_internal_xdg_surface_configure}; + +static void sl_internal_xdg_toplevel_configure( + void* data, + struct xdg_toplevel* xdg_toplevel, + int32_t width, + int32_t height, + struct wl_array* states) { + TRACE_EVENT("other", "sl_internal_xdg_toplevel_configure"); + struct sl_window* window = + static_cast(xdg_toplevel_get_user_data(xdg_toplevel)); + int activated = 0; + uint32_t* state; + int i = 0; + + if (!window->managed) + return; + + if (width && height) { + int32_t width_in_pixels = width; + int32_t height_in_pixels = height; + int i = 0; + + // We are receiving a request to resize a window (in logical dimensions) + // If the request is equal to the cached values we used to make adjustments + // do not recalculate the values + // However, if the request is not equal to the cached values, try + // and keep the buffer same size as what was previously set + // by the application. + struct sl_host_surface* paired_surface = window->paired_surface; + + if (paired_surface && paired_surface->has_own_scale) { + if (width != paired_surface->cached_logical_width || + height != paired_surface->cached_logical_height) { + sl_transform_try_window_scale(window->ctx, paired_surface, + window->width, window->height); + } + } + + sl_transform_host_to_guest(window->ctx, window->paired_surface, + &width_in_pixels, &height_in_pixels); + window->next_config.mask = XCB_CONFIG_WINDOW_WIDTH | + XCB_CONFIG_WINDOW_HEIGHT | + XCB_CONFIG_WINDOW_BORDER_WIDTH; + if (!(window->size_flags & (US_POSITION | P_POSITION))) { + window->next_config.mask |= XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y; + if (window->paired_surface && window->paired_surface->output) { + window->next_config.values[i++] = + window->paired_surface->output->virt_x + + (window->paired_surface->output->width - width_in_pixels) / 2; + window->next_config.values[i++] = + window->paired_surface->output->virt_y + + (window->paired_surface->output->height - height_in_pixels) / 2; + } else { + window->next_config.values[i++] = + window->ctx->screen->width_in_pixels / 2 - width_in_pixels / 2; + window->next_config.values[i++] = + window->ctx->screen->height_in_pixels / 2 - height_in_pixels / 2; + } + } + window->next_config.values[i++] = width_in_pixels; + window->next_config.values[i++] = height_in_pixels; + window->next_config.values[i++] = 0; + } + + window->allow_resize = 1; + window->compositor_fullscreen = 0; + sl_array_for_each(state, states) { + if (*state == XDG_TOPLEVEL_STATE_FULLSCREEN) { + window->allow_resize = 0; + window->next_config.states[i++] = + window->ctx->atoms[ATOM_NET_WM_STATE_FULLSCREEN].value; + window->compositor_fullscreen = 1; + } + if (*state == XDG_TOPLEVEL_STATE_MAXIMIZED) { + window->allow_resize = 0; + window->next_config.states[i++] = + window->ctx->atoms[ATOM_NET_WM_STATE_MAXIMIZED_VERT].value; + window->next_config.states[i++] = + window->ctx->atoms[ATOM_NET_WM_STATE_MAXIMIZED_HORZ].value; + } + if (*state == XDG_TOPLEVEL_STATE_ACTIVATED) { + activated = 1; + window->next_config.states[i++] = + window->ctx->atoms[ATOM_NET_WM_STATE_FOCUSED].value; + } + if (*state == XDG_TOPLEVEL_STATE_RESIZING) + window->allow_resize = 0; + } + + if (activated != window->activated) { + if (activated != (window->ctx->host_focus_window == window)) { + window->ctx->host_focus_window = activated ? window : NULL; + window->ctx->needs_set_input_focus = 1; + } + window->activated = activated; + } + + window->next_config.states_length = i; +} + +static void sl_internal_xdg_toplevel_close(void* data, + struct xdg_toplevel* xdg_toplevel) { + TRACE_EVENT("other", "sl_internal_xdg_toplevel_close"); + struct sl_window* window = + static_cast(xdg_toplevel_get_user_data(xdg_toplevel)); + xcb_client_message_event_t event = {}; + event.response_type = XCB_CLIENT_MESSAGE; + event.format = 32; + event.window = window->id; + event.type = window->ctx->atoms[ATOM_WM_PROTOCOLS].value; + event.data.data32[0] = window->ctx->atoms[ATOM_WM_DELETE_WINDOW].value; + event.data.data32[1] = XCB_CURRENT_TIME; + + xcb_send_event(window->ctx->connection, 0, window->id, + XCB_EVENT_MASK_NO_EVENT, (const char*)&event); +} + +static const struct xdg_toplevel_listener sl_internal_xdg_toplevel_listener = { + sl_internal_xdg_toplevel_configure, sl_internal_xdg_toplevel_close}; +void sl_update_application_id(struct sl_context* ctx, + struct sl_window* window) { + TRACE_EVENT("other", "sl_update_application_id"); + if (!window->aura_surface) + return; + if (ctx->application_id) { + zaura_surface_set_application_id(window->aura_surface, ctx->application_id); + return; + } + // Don't set application id for X11 override redirect. This prevents + // aura shell from thinking that these are regular application windows + // that should appear in application lists. + if (!ctx->xwayland || window->managed) { + char* application_id_str; + if (!window->app_id_property.empty()) { + application_id_str = + sl_xasprintf(X11_PROPERTY_APPLICATION_ID_FORMAT, ctx->vm_id, + window->app_id_property.c_str()); + } else if (window->clazz) { + application_id_str = sl_xasprintf(WM_CLASS_APPLICATION_ID_FORMAT, + ctx->vm_id, window->clazz); + } else if (window->client_leader != XCB_WINDOW_NONE) { + application_id_str = sl_xasprintf(WM_CLIENT_LEADER_APPLICATION_ID_FORMAT, + ctx->vm_id, window->client_leader); + } else { + application_id_str = + sl_xasprintf(XID_APPLICATION_ID_FORMAT, ctx->vm_id, window->id); + } + + zaura_surface_set_application_id(window->aura_surface, application_id_str); + free(application_id_str); + } +} + +void sl_window_update(struct sl_window* window) { + TRACE_EVENT("surface", "sl_window_update", "id", window->id); + struct wl_resource* host_resource = NULL; + struct sl_host_surface* host_surface; + struct sl_context* ctx = window->ctx; + struct sl_window* parent = NULL; + + if (window->host_surface_id) { + host_resource = wl_client_get_object(ctx->client, window->host_surface_id); + if (host_resource && window->unpaired) { + wl_list_remove(&window->link); + wl_list_insert(&ctx->windows, &window->link); + window->unpaired = 0; + } + } else if (!window->unpaired) { + wl_list_remove(&window->link); + wl_list_insert(&ctx->unpaired_windows, &window->link); + window->unpaired = 1; + window->paired_surface = NULL; + } + + if (!host_resource) { + if (window->aura_surface) { + zaura_surface_destroy(window->aura_surface); + window->aura_surface = NULL; + } + if (window->xdg_toplevel) { + xdg_toplevel_destroy(window->xdg_toplevel); + window->xdg_toplevel = NULL; + } + if (window->xdg_popup) { + xdg_popup_destroy(window->xdg_popup); + window->xdg_popup = NULL; + } + if (window->xdg_surface) { + xdg_surface_destroy(window->xdg_surface); + window->xdg_surface = NULL; + } + window->realized = 0; + return; + } + + host_surface = + static_cast(wl_resource_get_user_data(host_resource)); + assert(host_surface); + assert(!host_surface->has_role); + + if (!window->unpaired) { + window->paired_surface = host_surface; + sl_transform_try_window_scale(ctx, host_surface, window->width, + window->height); + } + + assert(ctx->xdg_shell); + assert(ctx->xdg_shell->internal); + + if (window->managed) { + if (window->transient_for != XCB_WINDOW_NONE) { + struct sl_window* sibling; + + wl_list_for_each(sibling, &ctx->windows, link) { + if (sibling->id == window->transient_for) { + if (sibling->xdg_toplevel) + parent = sibling; + break; + } + } + } + } + + // If we have a transient parent, but could not find it in the list of + // realized windows, then pick the window that had the last event for the + // parent. We update this again when we gain focus, so if we picked the wrong + // one it can get corrected at that point (but it's also possible the parent + // will never be realized, which is why selecting one here is important). + if (!window->managed || + (!parent && window->transient_for != XCB_WINDOW_NONE)) { + struct sl_window* sibling; + uint32_t parent_last_event_serial = 0; + + wl_list_for_each(sibling, &ctx->windows, link) { + struct wl_resource* sibling_host_resource; + struct sl_host_surface* sibling_host_surface; + + if (!sibling->realized) + continue; + + sibling_host_resource = + wl_client_get_object(ctx->client, sibling->host_surface_id); + if (!sibling_host_resource) + continue; + + // Any parent will do but prefer last event window. + sibling_host_surface = static_cast( + wl_resource_get_user_data(sibling_host_resource)); + if (parent_last_event_serial > sibling_host_surface->last_event_serial) + continue; + + // Do not use ourselves as the parent. + if (sibling->host_surface_id == window->host_surface_id) + continue; + + parent = sibling; + parent_last_event_serial = sibling_host_surface->last_event_serial; + } + } + + if (!window->depth) { + xcb_get_geometry_reply_t* geometry_reply = xcb_get_geometry_reply( + ctx->connection, xcb_get_geometry(ctx->connection, window->id), NULL); + if (geometry_reply) { + window->depth = geometry_reply->depth; + free(geometry_reply); + } + } + + if (!window->xdg_surface) { + window->xdg_surface = xdg_wm_base_get_xdg_surface(ctx->xdg_shell->internal, + host_surface->proxy); + xdg_surface_add_listener(window->xdg_surface, + &sl_internal_xdg_surface_listener, window); + } + + if (ctx->aura_shell) { + uint32_t frame_color; + + if (!window->aura_surface) { + window->aura_surface = zaura_shell_get_aura_surface( + ctx->aura_shell->internal, host_surface->proxy); + } + + zaura_surface_set_frame(window->aura_surface, + window->decorated + ? ZAURA_SURFACE_FRAME_TYPE_NORMAL + : window->depth == 32 + ? ZAURA_SURFACE_FRAME_TYPE_NONE + : ZAURA_SURFACE_FRAME_TYPE_SHADOW); + + frame_color = window->dark_frame ? ctx->dark_frame_color : ctx->frame_color; + zaura_surface_set_frame_colors(window->aura_surface, frame_color, + frame_color); + zaura_surface_set_startup_id(window->aura_surface, window->startup_id); + sl_update_application_id(ctx, window); + + if (ctx->aura_shell->version >= + ZAURA_SURFACE_SET_FULLSCREEN_MODE_SINCE_VERSION) { + zaura_surface_set_fullscreen_mode(window->aura_surface, + ctx->fullscreen_mode); + } + } + + // Always use top-level surface for X11 windows as we can't control when the + // window is closed. + if (ctx->xwayland || !parent) { + if (!window->xdg_toplevel) { + window->xdg_toplevel = xdg_surface_get_toplevel(window->xdg_surface); + xdg_toplevel_add_listener(window->xdg_toplevel, + &sl_internal_xdg_toplevel_listener, window); + } + if (parent) + xdg_toplevel_set_parent(window->xdg_toplevel, parent->xdg_toplevel); + if (window->name) + xdg_toplevel_set_title(window->xdg_toplevel, window->name); + if (window->size_flags & P_MIN_SIZE) { + int32_t minw = window->min_width; + int32_t minh = window->min_height; + + sl_transform_guest_to_host(window->ctx, window->paired_surface, &minw, + &minh); + xdg_toplevel_set_min_size(window->xdg_toplevel, minw, minh); + } + if (window->size_flags & P_MAX_SIZE) { + int32_t maxw = window->max_width; + int32_t maxh = window->max_height; + + sl_transform_guest_to_host(window->ctx, window->paired_surface, &maxw, + &maxh); + xdg_toplevel_set_max_size(window->xdg_toplevel, maxw, maxh); + } + if (window->maximized) { + xdg_toplevel_set_maximized(window->xdg_toplevel); + } + if (window->fullscreen) { + xdg_toplevel_set_fullscreen(window->xdg_toplevel, NULL); + } + } else if (!window->xdg_popup) { + struct xdg_positioner* positioner; + int32_t diffx = window->x - parent->x; + int32_t diffy = window->y - parent->y; + + positioner = xdg_wm_base_create_positioner(ctx->xdg_shell->internal); + assert(positioner); + + sl_transform_guest_to_host(window->ctx, window->paired_surface, &diffx, + &diffy); + xdg_positioner_set_anchor(positioner, XDG_POSITIONER_ANCHOR_TOP_LEFT); + xdg_positioner_set_gravity(positioner, XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT); + xdg_positioner_set_anchor_rect(positioner, diffx, diffy, 1, 1); + + window->xdg_popup = xdg_surface_get_popup(window->xdg_surface, + parent->xdg_surface, positioner); + xdg_popup_add_listener(window->xdg_popup, &sl_internal_xdg_popup_listener, + window); + + xdg_positioner_destroy(positioner); + } + + if ((window->size_flags & (US_POSITION | P_POSITION)) && parent && + ctx->aura_shell) { + int32_t diffx = window->x - parent->x; + int32_t diffy = window->y - parent->y; + + sl_transform_guest_to_host(window->ctx, window->paired_surface, &diffx, + &diffy); + zaura_surface_set_parent(window->aura_surface, parent->aura_surface, diffx, + diffy); + } + +#ifdef COMMIT_LOOP_FIX + sl_commit(window, host_surface); +#else + wl_surface_commit(host_surface->proxy); +#endif + + if (host_surface->contents_width && host_surface->contents_height) + window->realized = 1; +} diff --git a/sommelier/sommelier-window.h b/sommelier/sommelier-window.h new file mode 100644 index 0000000..79e529f --- /dev/null +++ b/sommelier/sommelier-window.h @@ -0,0 +1,198 @@ +// Copyright 2021 The ChromiumOS Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef VM_TOOLS_SOMMELIER_SOMMELIER_WINDOW_H_ +#define VM_TOOLS_SOMMELIER_SOMMELIER_WINDOW_H_ + +#include +#include +#include +#include + +#define US_POSITION (1L << 0) +#define US_SIZE (1L << 1) +#define P_POSITION (1L << 2) +#define P_SIZE (1L << 3) +#define P_MIN_SIZE (1L << 4) +#define P_MAX_SIZE (1L << 5) +#define P_RESIZE_INC (1L << 6) +#define P_ASPECT (1L << 7) +#define P_BASE_SIZE (1L << 8) +#define P_WIN_GRAVITY (1L << 9) + +struct sl_config { + uint32_t serial = 0; + uint32_t mask = 0; + uint32_t values[5]; + uint32_t states_length = 0; + uint32_t states[3]; +}; + +struct sl_host_surface; + +struct sl_window { + sl_window(struct sl_context* ctx, + xcb_window_t id, + int x, + int y, + int width, + int height, + int border_width); + ~sl_window(); + + struct sl_context* ctx = nullptr; + xcb_window_t id = XCB_WINDOW_NONE; + xcb_window_t frame_id = XCB_WINDOW_NONE; + uint32_t host_surface_id = 0; + int unpaired = 1; + bool shaped = false; + int x = 0; + int y = 0; + int width = 0; + int height = 0; + int border_width = 0; + int depth = 0; + int managed = 0; + int realized = 0; + int activated = 0; + int fullscreen = 0; + int compositor_fullscreen = 0; + int maximized = 0; + int iconified = 0; + // True if there has been changes to the fullscreen/maximized state + // while this window is iconified. + bool pending_fullscreen_change = false; + bool pending_maximized_change = false; + int allow_resize = 1; + xcb_window_t transient_for = XCB_WINDOW_NONE; + xcb_window_t client_leader = XCB_WINDOW_NONE; + int decorated = 0; + char* name = nullptr; + bool has_net_wm_name = false; + char* clazz = nullptr; + char* startup_id = nullptr; + std::string app_id_property; + int dark_frame = 0; + uint32_t size_flags = P_POSITION; + int focus_model_take_focus = 0; + int min_width = 0; + int min_height = 0; + int max_width = 0; + int max_height = 0; + struct sl_config next_config; + struct sl_config pending_config; + struct xdg_surface* xdg_surface = nullptr; + struct xdg_toplevel* xdg_toplevel = nullptr; + struct xdg_popup* xdg_popup = nullptr; + struct zaura_surface* aura_surface = nullptr; + struct sl_host_surface* paired_surface = nullptr; + struct pixman_region32 shape_rectangles; + struct wl_list link = {}; +}; + +enum { + PROPERTY_WM_NAME, + PROPERTY_NET_WM_NAME, + PROPERTY_WM_CLASS, + PROPERTY_WM_TRANSIENT_FOR, + PROPERTY_WM_NORMAL_HINTS, + PROPERTY_WM_CLIENT_LEADER, + PROPERTY_WM_PROTOCOLS, + PROPERTY_MOTIF_WM_HINTS, + PROPERTY_NET_STARTUP_ID, + PROPERTY_NET_WM_STATE, + PROPERTY_GTK_THEME_VARIANT, + PROPERTY_XWAYLAND_RANDR_EMU_MONITOR_RECTS, + + // The atom corresponding to this property changes depending on the + // --application-id-format command-line argument. + PROPERTY_SPECIFIED_FOR_APP_ID, +}; + +struct sl_wm_size_hints { + uint32_t flags; + int32_t x, y; + int32_t width, height; + int32_t min_width, min_height; + int32_t max_width, max_height; + int32_t width_inc, height_inc; + struct { + int32_t x; + int32_t y; + } min_aspect, max_aspect; + int32_t base_width, base_height; + int32_t win_gravity; +}; + +// WM_HINTS is defined at: https://tronche.com/gui/x/icccm/sec-4.html + +#define WM_HINTS_FLAG_INPUT (1L << 0) +#define WM_HINTS_FLAG_STATE (1L << 1) +#define WM_HINTS_FLAG_ICON_PIXMAP (1L << 2) +#define WM_HINTS_FLAG_ICON_WINDOW (1L << 3) +#define WM_HINTS_FLAG_ICON_POSITION (1L << 4) +#define WM_HINTS_FLAG_ICON_MASK (1L << 5) +#define WM_HINTS_FLAG_WINDOW_GROUP (1L << 6) +#define WM_HINTS_FLAG_MESSAGE (1L << 7) +#define WM_HINTS_FLAG_URGENCY (1L << 8) + +struct sl_wm_hints { + uint32_t flags; + uint32_t input; + uint32_t initial_state; + xcb_pixmap_t icon_pixmap; + xcb_window_t icon_window; + int32_t icon_x; + int32_t icon_y; + xcb_pixmap_t icon_mask; +}; + +#define MWM_HINTS_FUNCTIONS (1L << 0) +#define MWM_HINTS_DECORATIONS (1L << 1) +#define MWM_HINTS_INPUT_MODE (1L << 2) +#define MWM_HINTS_STATUS (1L << 3) + +#define MWM_DECOR_ALL (1L << 0) +#define MWM_DECOR_BORDER (1L << 1) +#define MWM_DECOR_RESIZEH (1L << 2) +#define MWM_DECOR_TITLE (1L << 3) +#define MWM_DECOR_MENU (1L << 4) +#define MWM_DECOR_MINIMIZE (1L << 5) +#define MWM_DECOR_MAXIMIZE (1L << 6) + +struct sl_mwm_hints { + uint32_t flags; + uint32_t functions; + uint32_t decorations; + int32_t input_mode; + uint32_t status; +}; + +#define NET_WM_MOVERESIZE_SIZE_TOPLEFT 0 +#define NET_WM_MOVERESIZE_SIZE_TOP 1 +#define NET_WM_MOVERESIZE_SIZE_TOPRIGHT 2 +#define NET_WM_MOVERESIZE_SIZE_RIGHT 3 +#define NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT 4 +#define NET_WM_MOVERESIZE_SIZE_BOTTOM 5 +#define NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT 6 +#define NET_WM_MOVERESIZE_SIZE_LEFT 7 +#define NET_WM_MOVERESIZE_MOVE 8 + +#define NET_WM_STATE_REMOVE 0 +#define NET_WM_STATE_ADD 1 +#define NET_WM_STATE_TOGGLE 2 + +#define WM_STATE_WITHDRAWN 0 +#define WM_STATE_NORMAL 1 +#define WM_STATE_ICONIC 3 + +void sl_window_update(struct sl_window* window); +void sl_update_application_id(struct sl_context* ctx, struct sl_window* window); +void sl_configure_window(struct sl_window* window); +void sl_send_configure_notify(struct sl_window* window); + +int sl_process_pending_configure_acks(struct sl_window* window, + struct sl_host_surface* host_surface); + +#endif // VM_TOOLS_SOMMELIER_SOMMELIER_WINDOW_H_ diff --git a/sommelier/sommelier-xdg-shell.c b/sommelier/sommelier-xdg-shell.c deleted file mode 100644 index ecd0fc6..0000000 --- a/sommelier/sommelier-xdg-shell.c +++ /dev/null @@ -1,563 +0,0 @@ -// Copyright 2018 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "sommelier.h" - -#include -#include - -#include "xdg-shell-unstable-v6-client-protocol.h" -#include "xdg-shell-unstable-v6-server-protocol.h" - -struct sl_host_xdg_shell { - struct sl_context* ctx; - struct wl_resource* resource; - struct zxdg_shell_v6* proxy; -}; - -struct sl_host_xdg_surface { - struct sl_context* ctx; - struct wl_resource* resource; - struct zxdg_surface_v6* proxy; -}; - -struct sl_host_xdg_toplevel { - struct sl_context* ctx; - struct wl_resource* resource; - struct zxdg_toplevel_v6* proxy; -}; - -struct sl_host_xdg_popup { - struct sl_context* ctx; - struct wl_resource* resource; - struct zxdg_popup_v6* proxy; -}; - -struct sl_host_xdg_positioner { - struct sl_context* ctx; - struct wl_resource* resource; - struct zxdg_positioner_v6* proxy; -}; - -static void sl_xdg_positioner_destroy(struct wl_client* client, - struct wl_resource* resource) { - wl_resource_destroy(resource); -} - -static void sl_xdg_positioner_set_size(struct wl_client* client, - struct wl_resource* resource, - int32_t width, - int32_t height) { - struct sl_host_xdg_positioner* host = wl_resource_get_user_data(resource); - double scale = host->ctx->scale; - - zxdg_positioner_v6_set_size(host->proxy, width / scale, height / scale); -} - -static void sl_xdg_positioner_set_anchor_rect(struct wl_client* client, - struct wl_resource* resource, - int32_t x, - int32_t y, - int32_t width, - int32_t height) { - struct sl_host_xdg_positioner* host = wl_resource_get_user_data(resource); - double scale = host->ctx->scale; - int32_t x1, y1, x2, y2; - - x1 = x / scale; - y1 = y / scale; - x2 = (x + width) / scale; - y2 = (y + height) / scale; - - zxdg_positioner_v6_set_anchor_rect(host->proxy, x1, y1, x2 - x1, y2 - y1); -} - -static void sl_xdg_positioner_set_anchor(struct wl_client* client, - struct wl_resource* resource, - uint32_t anchor) { - struct sl_host_xdg_positioner* host = wl_resource_get_user_data(resource); - - zxdg_positioner_v6_set_anchor(host->proxy, anchor); -} - -static void sl_xdg_positioner_set_gravity(struct wl_client* client, - struct wl_resource* resource, - uint32_t gravity) { - struct sl_host_xdg_positioner* host = wl_resource_get_user_data(resource); - - zxdg_positioner_v6_set_gravity(host->proxy, gravity); -} - -static void sl_xdg_positioner_set_constraint_adjustment( - struct wl_client* client, - struct wl_resource* resource, - uint32_t constraint_adjustment) { - struct sl_host_xdg_positioner* host = wl_resource_get_user_data(resource); - - zxdg_positioner_v6_set_constraint_adjustment(host->proxy, - constraint_adjustment); -} - -static void sl_xdg_positioner_set_offset(struct wl_client* client, - struct wl_resource* resource, - int32_t x, - int32_t y) { - struct sl_host_xdg_positioner* host = wl_resource_get_user_data(resource); - double scale = host->ctx->scale; - - zxdg_positioner_v6_set_offset(host->proxy, x / scale, y / scale); -} - -static const struct zxdg_positioner_v6_interface - sl_xdg_positioner_implementation = { - sl_xdg_positioner_destroy, - sl_xdg_positioner_set_size, - sl_xdg_positioner_set_anchor_rect, - sl_xdg_positioner_set_anchor, - sl_xdg_positioner_set_gravity, - sl_xdg_positioner_set_constraint_adjustment, - sl_xdg_positioner_set_offset}; - -static void sl_destroy_host_xdg_positioner(struct wl_resource* resource) { - struct sl_host_xdg_positioner* host = wl_resource_get_user_data(resource); - - zxdg_positioner_v6_destroy(host->proxy); - wl_resource_set_user_data(resource, NULL); - free(host); -} - -static void sl_xdg_popup_destroy(struct wl_client* client, - struct wl_resource* resource) { - wl_resource_destroy(resource); -} - -static void sl_xdg_popup_grab(struct wl_client* client, - struct wl_resource* resource, - struct wl_resource* seat_resource, - uint32_t serial) { - struct sl_host_xdg_popup* host = wl_resource_get_user_data(resource); - struct sl_host_seat* host_seat = wl_resource_get_user_data(seat_resource); - - zxdg_popup_v6_grab(host->proxy, host_seat->proxy, serial); -} - -static const struct zxdg_popup_v6_interface sl_xdg_popup_implementation = { - sl_xdg_popup_destroy, sl_xdg_popup_grab}; - -static void sl_xdg_popup_configure(void* data, - struct zxdg_popup_v6* xdg_popup, - int32_t x, - int32_t y, - int32_t width, - int32_t height) { - struct sl_host_xdg_popup* host = zxdg_popup_v6_get_user_data(xdg_popup); - double scale = host->ctx->scale; - int32_t x1, y1, x2, y2; - - x1 = x * scale; - y1 = y * scale; - x2 = (x + width) * scale; - y2 = (y + height) * scale; - - zxdg_popup_v6_send_configure(host->resource, x1, y1, x2 - x1, y2 - y1); -} - -static void sl_xdg_popup_popup_done(void* data, - struct zxdg_popup_v6* xdg_popup) { - struct sl_host_xdg_popup* host = zxdg_popup_v6_get_user_data(xdg_popup); - - zxdg_popup_v6_send_popup_done(host->resource); -} - -static const struct zxdg_popup_v6_listener sl_xdg_popup_listener = { - sl_xdg_popup_configure, sl_xdg_popup_popup_done}; - -static void sl_destroy_host_xdg_popup(struct wl_resource* resource) { - struct sl_host_xdg_popup* host = wl_resource_get_user_data(resource); - - zxdg_popup_v6_destroy(host->proxy); - wl_resource_set_user_data(resource, NULL); - free(host); -} - -static void sl_xdg_toplevel_destroy(struct wl_client* client, - struct wl_resource* resource) { - wl_resource_destroy(resource); -} - -static void sl_xdg_toplevel_set_parent(struct wl_client* client, - struct wl_resource* resource, - struct wl_resource* parent_resource) { - struct sl_host_xdg_toplevel* host = wl_resource_get_user_data(resource); - struct sl_host_xdg_toplevel* host_parent = - parent_resource ? wl_resource_get_user_data(parent_resource) : NULL; - - zxdg_toplevel_v6_set_parent(host->proxy, - host_parent ? host_parent->proxy : NULL); -} - -static void sl_xdg_toplevel_set_title(struct wl_client* client, - struct wl_resource* resource, - const char* title) { - struct sl_host_xdg_toplevel* host = wl_resource_get_user_data(resource); - - zxdg_toplevel_v6_set_title(host->proxy, title); -} - -static void sl_xdg_toplevel_set_app_id(struct wl_client* client, - struct wl_resource* resource, - const char* app_id) { - struct sl_host_xdg_toplevel* host = wl_resource_get_user_data(resource); - - zxdg_toplevel_v6_set_app_id(host->proxy, app_id); -} - -static void sl_xdg_toplevel_show_window_menu(struct wl_client* client, - struct wl_resource* resource, - struct wl_resource* seat_resource, - uint32_t serial, - int32_t x, - int32_t y) { - struct sl_host_xdg_toplevel* host = wl_resource_get_user_data(resource); - struct sl_host_seat* host_seat = - seat_resource ? wl_resource_get_user_data(seat_resource) : NULL; - - zxdg_toplevel_v6_show_window_menu( - host->proxy, host_seat ? host_seat->proxy : NULL, serial, x, y); -} - -static void sl_xdg_toplevel_move(struct wl_client* client, - struct wl_resource* resource, - struct wl_resource* seat_resource, - uint32_t serial) { - struct sl_host_xdg_toplevel* host = wl_resource_get_user_data(resource); - struct sl_host_seat* host_seat = - seat_resource ? wl_resource_get_user_data(seat_resource) : NULL; - - zxdg_toplevel_v6_move(host->proxy, host_seat ? host_seat->proxy : NULL, - serial); -} - -static void sl_xdg_toplevel_resize(struct wl_client* client, - struct wl_resource* resource, - struct wl_resource* seat_resource, - uint32_t serial, - uint32_t edges) { - struct sl_host_xdg_toplevel* host = wl_resource_get_user_data(resource); - struct sl_host_seat* host_seat = - seat_resource ? wl_resource_get_user_data(seat_resource) : NULL; - - zxdg_toplevel_v6_resize(host->proxy, host_seat ? host_seat->proxy : NULL, - serial, edges); -} - -static void sl_xdg_toplevel_set_max_size(struct wl_client* client, - struct wl_resource* resource, - int32_t width, - int32_t height) { - struct sl_host_xdg_toplevel* host = wl_resource_get_user_data(resource); - - zxdg_toplevel_v6_set_max_size(host->proxy, width, height); -} - -static void sl_xdg_toplevel_set_min_size(struct wl_client* client, - struct wl_resource* resource, - int32_t width, - int32_t height) { - struct sl_host_xdg_toplevel* host = wl_resource_get_user_data(resource); - - zxdg_toplevel_v6_set_min_size(host->proxy, width, height); -} - -static void sl_xdg_toplevel_set_maximized(struct wl_client* client, - struct wl_resource* resource) { - struct sl_host_xdg_toplevel* host = wl_resource_get_user_data(resource); - - zxdg_toplevel_v6_set_maximized(host->proxy); -} - -static void sl_xdg_toplevel_unset_maximized(struct wl_client* client, - struct wl_resource* resource) { - struct sl_host_xdg_toplevel* host = wl_resource_get_user_data(resource); - - zxdg_toplevel_v6_unset_maximized(host->proxy); -} - -static void sl_xdg_toplevel_set_fullscreen( - struct wl_client* client, - struct wl_resource* resource, - struct wl_resource* output_resource) { - struct sl_host_xdg_toplevel* host = wl_resource_get_user_data(resource); - struct sl_host_output* host_output = - output_resource ? wl_resource_get_user_data(output_resource) : NULL; - - zxdg_toplevel_v6_set_fullscreen(host->proxy, - host_output ? host_output->proxy : NULL); -} - -static void sl_xdg_toplevel_unset_fullscreen(struct wl_client* client, - struct wl_resource* resource) { - struct sl_host_xdg_toplevel* host = wl_resource_get_user_data(resource); - - zxdg_toplevel_v6_unset_fullscreen(host->proxy); -} - -static void sl_xdg_toplevel_set_minimized(struct wl_client* client, - struct wl_resource* resource) { - struct sl_host_xdg_toplevel* host = wl_resource_get_user_data(resource); - - zxdg_toplevel_v6_set_minimized(host->proxy); -} - -static const struct zxdg_toplevel_v6_interface sl_xdg_toplevel_implementation = - {sl_xdg_toplevel_destroy, sl_xdg_toplevel_set_parent, - sl_xdg_toplevel_set_title, sl_xdg_toplevel_set_app_id, - sl_xdg_toplevel_show_window_menu, sl_xdg_toplevel_move, - sl_xdg_toplevel_resize, sl_xdg_toplevel_set_max_size, - sl_xdg_toplevel_set_min_size, sl_xdg_toplevel_set_maximized, - sl_xdg_toplevel_unset_maximized, sl_xdg_toplevel_set_fullscreen, - sl_xdg_toplevel_unset_fullscreen, sl_xdg_toplevel_set_minimized}; - -static void sl_xdg_toplevel_configure(void* data, - struct zxdg_toplevel_v6* xdg_toplevel, - int32_t width, - int32_t height, - struct wl_array* states) { - struct sl_host_xdg_toplevel* host = - zxdg_toplevel_v6_get_user_data(xdg_toplevel); - double scale = host->ctx->scale; - - zxdg_toplevel_v6_send_configure(host->resource, width * scale, height * scale, - states); -} - -static void sl_xdg_toplevel_close(void* data, - struct zxdg_toplevel_v6* xdg_toplevel) { - struct sl_host_xdg_toplevel* host = - zxdg_toplevel_v6_get_user_data(xdg_toplevel); - - zxdg_toplevel_v6_send_close(host->resource); -} - -static const struct zxdg_toplevel_v6_listener sl_xdg_toplevel_listener = { - sl_xdg_toplevel_configure, sl_xdg_toplevel_close}; - -static void sl_destroy_host_xdg_toplevel(struct wl_resource* resource) { - struct sl_host_xdg_toplevel* host = wl_resource_get_user_data(resource); - - zxdg_toplevel_v6_destroy(host->proxy); - wl_resource_set_user_data(resource, NULL); - free(host); -} - -static void sl_xdg_surface_destroy(struct wl_client* client, - struct wl_resource* resource) { - wl_resource_destroy(resource); -} - -static void sl_xdg_surface_get_toplevel(struct wl_client* client, - struct wl_resource* resource, - uint32_t id) { - struct sl_host_xdg_surface* host = wl_resource_get_user_data(resource); - struct sl_host_xdg_toplevel* host_xdg_toplevel; - - host_xdg_toplevel = malloc(sizeof(*host_xdg_toplevel)); - assert(host_xdg_toplevel); - - host_xdg_toplevel->ctx = host->ctx; - host_xdg_toplevel->resource = - wl_resource_create(client, &zxdg_toplevel_v6_interface, 1, id); - wl_resource_set_implementation( - host_xdg_toplevel->resource, &sl_xdg_toplevel_implementation, - host_xdg_toplevel, sl_destroy_host_xdg_toplevel); - host_xdg_toplevel->proxy = zxdg_surface_v6_get_toplevel(host->proxy); - zxdg_toplevel_v6_set_user_data(host_xdg_toplevel->proxy, host_xdg_toplevel); - zxdg_toplevel_v6_add_listener(host_xdg_toplevel->proxy, - &sl_xdg_toplevel_listener, host_xdg_toplevel); -} - -static void sl_xdg_surface_get_popup(struct wl_client* client, - struct wl_resource* resource, - uint32_t id, - struct wl_resource* parent_resource, - struct wl_resource* positioner_resource) { - struct sl_host_xdg_surface* host = wl_resource_get_user_data(resource); - struct sl_host_xdg_surface* host_parent = - wl_resource_get_user_data(parent_resource); - struct sl_host_xdg_positioner* host_positioner = - wl_resource_get_user_data(positioner_resource); - struct sl_host_xdg_popup* host_xdg_popup; - - host_xdg_popup = malloc(sizeof(*host_xdg_popup)); - assert(host_xdg_popup); - - host_xdg_popup->ctx = host->ctx; - host_xdg_popup->resource = - wl_resource_create(client, &zxdg_popup_v6_interface, 1, id); - wl_resource_set_implementation(host_xdg_popup->resource, - &sl_xdg_popup_implementation, host_xdg_popup, - sl_destroy_host_xdg_popup); - host_xdg_popup->proxy = zxdg_surface_v6_get_popup( - host->proxy, host_parent->proxy, host_positioner->proxy); - zxdg_popup_v6_set_user_data(host_xdg_popup->proxy, host_xdg_popup); - zxdg_popup_v6_add_listener(host_xdg_popup->proxy, &sl_xdg_popup_listener, - host_xdg_popup); -} - -static void sl_xdg_surface_set_window_geometry(struct wl_client* client, - struct wl_resource* resource, - int32_t x, - int32_t y, - int32_t width, - int32_t height) { - struct sl_host_xdg_surface* host = wl_resource_get_user_data(resource); - double scale = host->ctx->scale; - int32_t x1, y1, x2, y2; - - x1 = x / scale; - y1 = y / scale; - x2 = (x + width) / scale; - y2 = (y + height) / scale; - - zxdg_surface_v6_set_window_geometry(host->proxy, x1, y1, x2 - x1, y2 - y1); -} - -static void sl_xdg_surface_ack_configure(struct wl_client* client, - struct wl_resource* resource, - uint32_t serial) { - struct sl_host_xdg_surface* host = wl_resource_get_user_data(resource); - - zxdg_surface_v6_ack_configure(host->proxy, serial); -} - -static const struct zxdg_surface_v6_interface sl_xdg_surface_implementation = { - sl_xdg_surface_destroy, sl_xdg_surface_get_toplevel, - sl_xdg_surface_get_popup, sl_xdg_surface_set_window_geometry, - sl_xdg_surface_ack_configure}; - -static void sl_xdg_surface_configure(void* data, - struct zxdg_surface_v6* xdg_surface, - uint32_t serial) { - struct sl_host_xdg_surface* host = zxdg_surface_v6_get_user_data(xdg_surface); - - zxdg_surface_v6_send_configure(host->resource, serial); -} - -static const struct zxdg_surface_v6_listener sl_xdg_surface_listener = { - sl_xdg_surface_configure}; - -static void sl_destroy_host_xdg_surface(struct wl_resource* resource) { - struct sl_host_xdg_surface* host = wl_resource_get_user_data(resource); - - zxdg_surface_v6_destroy(host->proxy); - wl_resource_set_user_data(resource, NULL); - free(host); -} - -static void sl_xdg_shell_destroy(struct wl_client* client, - struct wl_resource* resource) { - wl_resource_destroy(resource); -} - -static void sl_xdg_shell_create_positioner(struct wl_client* client, - struct wl_resource* resource, - uint32_t id) { - struct sl_host_xdg_shell* host = wl_resource_get_user_data(resource); - struct sl_host_xdg_positioner* host_xdg_positioner; - - host_xdg_positioner = malloc(sizeof(*host_xdg_positioner)); - assert(host_xdg_positioner); - - host_xdg_positioner->ctx = host->ctx; - host_xdg_positioner->resource = - wl_resource_create(client, &zxdg_positioner_v6_interface, 1, id); - wl_resource_set_implementation( - host_xdg_positioner->resource, &sl_xdg_positioner_implementation, - host_xdg_positioner, sl_destroy_host_xdg_positioner); - host_xdg_positioner->proxy = zxdg_shell_v6_create_positioner(host->proxy); - zxdg_positioner_v6_set_user_data(host_xdg_positioner->proxy, - host_xdg_positioner); -} - -static void sl_xdg_shell_get_xdg_surface(struct wl_client* client, - struct wl_resource* resource, - uint32_t id, - struct wl_resource* surface_resource) { - struct sl_host_xdg_shell* host = wl_resource_get_user_data(resource); - struct sl_host_surface* host_surface = - wl_resource_get_user_data(surface_resource); - struct sl_host_xdg_surface* host_xdg_surface; - - host_xdg_surface = malloc(sizeof(*host_xdg_surface)); - assert(host_xdg_surface); - - host_xdg_surface->ctx = host->ctx; - host_xdg_surface->resource = - wl_resource_create(client, &zxdg_surface_v6_interface, 1, id); - wl_resource_set_implementation(host_xdg_surface->resource, - &sl_xdg_surface_implementation, - host_xdg_surface, sl_destroy_host_xdg_surface); - host_xdg_surface->proxy = - zxdg_shell_v6_get_xdg_surface(host->proxy, host_surface->proxy); - zxdg_surface_v6_set_user_data(host_xdg_surface->proxy, host_xdg_surface); - zxdg_surface_v6_add_listener(host_xdg_surface->proxy, - &sl_xdg_surface_listener, host_xdg_surface); - host_surface->has_role = 1; -} - -static void sl_xdg_shell_pong(struct wl_client* client, - struct wl_resource* resource, - uint32_t serial) { - struct sl_host_xdg_shell* host = wl_resource_get_user_data(resource); - - zxdg_shell_v6_pong(host->proxy, serial); -} - -static const struct zxdg_shell_v6_interface sl_xdg_shell_implementation = { - sl_xdg_shell_destroy, sl_xdg_shell_create_positioner, - sl_xdg_shell_get_xdg_surface, sl_xdg_shell_pong}; - -static void sl_xdg_shell_ping(void* data, - struct zxdg_shell_v6* xdg_shell, - uint32_t serial) { - struct sl_host_xdg_shell* host = zxdg_shell_v6_get_user_data(xdg_shell); - - zxdg_shell_v6_send_ping(host->resource, serial); -} - -static const struct zxdg_shell_v6_listener sl_xdg_shell_listener = { - sl_xdg_shell_ping}; - -static void sl_destroy_host_xdg_shell(struct wl_resource* resource) { - struct sl_host_xdg_shell* host = wl_resource_get_user_data(resource); - - zxdg_shell_v6_destroy(host->proxy); - wl_resource_set_user_data(resource, NULL); - free(host); -} - -static void sl_bind_host_xdg_shell(struct wl_client* client, - void* data, - uint32_t version, - uint32_t id) { - struct sl_context* ctx = (struct sl_context*)data; - struct sl_host_xdg_shell* host; - - host = malloc(sizeof(*host)); - assert(host); - host->ctx = ctx; - host->resource = wl_resource_create(client, &zxdg_shell_v6_interface, 1, id); - wl_resource_set_implementation(host->resource, &sl_xdg_shell_implementation, - host, sl_destroy_host_xdg_shell); - host->proxy = - wl_registry_bind(wl_display_get_registry(ctx->display), - ctx->xdg_shell->id, &zxdg_shell_v6_interface, 1); - zxdg_shell_v6_set_user_data(host->proxy, host); - zxdg_shell_v6_add_listener(host->proxy, &sl_xdg_shell_listener, host); -} - -struct sl_global* sl_xdg_shell_global_create(struct sl_context* ctx) { - return sl_global_create(ctx, &zxdg_shell_v6_interface, 1, ctx, - sl_bind_host_xdg_shell); -} diff --git a/sommelier/sommelier-xdg-shell.cc b/sommelier/sommelier-xdg-shell.cc new file mode 100644 index 0000000..275b798 --- /dev/null +++ b/sommelier/sommelier-xdg-shell.cc @@ -0,0 +1,450 @@ +// Copyright 2018 The ChromiumOS Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sommelier.h" // NOLINT(build/include_directory) +#include "sommelier-transform.h" // NOLINT(build/include_directory) + +#include +#include + +#include "xdg-shell-client-protocol.h" // NOLINT(build/include_directory) +#include "xdg-shell-server-protocol.h" // NOLINT(build/include_directory) + +struct sl_host_xdg_shell { + struct sl_context* ctx; + struct wl_resource* resource; + struct xdg_wm_base* proxy; +}; +MAP_STRUCTS(xdg_wm_base, sl_host_xdg_shell); + +struct sl_host_xdg_surface { + struct sl_context* ctx; + struct wl_resource* resource; + struct xdg_surface* proxy; + struct sl_host_surface* originator; +}; +MAP_STRUCTS(xdg_surface, sl_host_xdg_surface); + +struct sl_host_xdg_toplevel { + struct sl_context* ctx; + struct wl_resource* resource; + struct xdg_toplevel* proxy; + struct sl_host_xdg_surface* originator; +}; +MAP_STRUCTS(xdg_toplevel, sl_host_xdg_toplevel); + +struct sl_host_xdg_popup { + struct sl_context* ctx; + struct wl_resource* resource; + struct xdg_popup* proxy; + struct sl_host_xdg_surface* originator; +}; +MAP_STRUCTS(xdg_popup, sl_host_xdg_popup); + +struct sl_host_xdg_positioner { + struct sl_context* ctx; + struct wl_resource* resource; + struct xdg_positioner* proxy; +}; +MAP_STRUCTS(xdg_positioner, sl_host_xdg_positioner); + +static void sl_xdg_positioner_destroy(struct wl_client* client, + struct wl_resource* resource) { + wl_resource_destroy(resource); +} + +static void sl_xdg_positioner_set_size(struct wl_client* client, + struct wl_resource* resource, + int32_t width, + int32_t height) { + struct sl_host_xdg_positioner* host = + static_cast(wl_resource_get_user_data(resource)); + + int32_t iwidth = width; + int32_t iheight = height; + + sl_transform_guest_to_host(host->ctx, nullptr, &iwidth, &iheight); + xdg_positioner_set_size(host->proxy, iwidth, iheight); +} + +static void sl_xdg_positioner_set_anchor_rect(struct wl_client* client, + struct wl_resource* resource, + int32_t x, + int32_t y, + int32_t width, + int32_t height) { + struct sl_host_xdg_positioner* host = + static_cast(wl_resource_get_user_data(resource)); + int32_t x1 = x; + int32_t y1 = y; + int32_t x2 = x + width; + int32_t y2 = y + height; + + sl_transform_guest_to_host(host->ctx, nullptr, &x1, &y1); + sl_transform_guest_to_host(host->ctx, nullptr, &x2, &y2); + + xdg_positioner_set_anchor_rect(host->proxy, x1, y1, x2 - x1, y2 - y1); +} + +static void sl_xdg_positioner_set_offset(struct wl_client* client, + struct wl_resource* resource, + int32_t x, + int32_t y) { + struct sl_host_xdg_positioner* host = + static_cast(wl_resource_get_user_data(resource)); + int32_t ix = x, iy = y; + + sl_transform_guest_to_host(host->ctx, nullptr, &ix, &iy); + xdg_positioner_set_offset(host->proxy, ix, iy); +} + +static const struct xdg_positioner_interface sl_xdg_positioner_implementation = + {sl_xdg_positioner_destroy, + sl_xdg_positioner_set_size, + sl_xdg_positioner_set_anchor_rect, + ForwardRequest, + ForwardRequest, + ForwardRequest, + sl_xdg_positioner_set_offset}; + +static void sl_destroy_host_xdg_positioner(struct wl_resource* resource) { + struct sl_host_xdg_positioner* host = + static_cast(wl_resource_get_user_data(resource)); + + xdg_positioner_destroy(host->proxy); + wl_resource_set_user_data(resource, NULL); + delete host; +} + +static void sl_xdg_popup_destroy(struct wl_client* client, + struct wl_resource* resource) { + wl_resource_destroy(resource); +} + +static const struct xdg_popup_interface sl_xdg_popup_implementation = { + sl_xdg_popup_destroy, ForwardRequest}; + +static struct sl_host_surface* get_host_surface( + struct sl_host_xdg_surface* xdg) { + // For xdg_popup/xdg_toplevel they will point to the + // originating xdg_surface. The originating surface + // will point to the source sl_host_surface + if (xdg && xdg->originator) + return xdg->originator; + else + return nullptr; +} + +static void sl_xdg_popup_configure(void* data, + struct xdg_popup* xdg_popup, + int32_t x, + int32_t y, + int32_t width, + int32_t height) { + struct sl_host_xdg_popup* host = + static_cast(xdg_popup_get_user_data(xdg_popup)); + int32_t x1 = x; + int32_t y1 = y; + int32_t x2 = x + width; + int32_t y2 = y + height; + + sl_transform_host_to_guest(host->ctx, get_host_surface(host->originator), &x1, + &y1); + sl_transform_host_to_guest(host->ctx, get_host_surface(host->originator), &x2, + &y2); + + xdg_popup_send_configure(host->resource, x1, y1, x2 - x1, y2 - y1); +} + +static void sl_xdg_popup_popup_done(void* data, struct xdg_popup* xdg_popup) { + struct sl_host_xdg_popup* host = + static_cast(xdg_popup_get_user_data(xdg_popup)); + + xdg_popup_send_popup_done(host->resource); +} + +static const struct xdg_popup_listener sl_xdg_popup_listener = { + sl_xdg_popup_configure, sl_xdg_popup_popup_done}; + +static void sl_destroy_host_xdg_popup(struct wl_resource* resource) { + struct sl_host_xdg_popup* host = + static_cast(wl_resource_get_user_data(resource)); + + xdg_popup_destroy(host->proxy); + wl_resource_set_user_data(resource, NULL); + delete host; +} + +static void sl_xdg_toplevel_destroy(struct wl_client* client, + struct wl_resource* resource) { + wl_resource_destroy(resource); +} + +static void sl_xdg_toplevel_show_window_menu(struct wl_client* client, + struct wl_resource* resource, + struct wl_resource* seat_resource, + uint32_t serial, + int32_t x, + int32_t y) { + struct sl_host_xdg_toplevel* host = + static_cast(wl_resource_get_user_data(resource)); + struct sl_host_seat* host_seat = + seat_resource + ? static_cast(wl_resource_get_user_data(seat_resource)) + : NULL; + + // TODO(mrisaacb): There was no scaling performed here in the original code. + // Figure out why this was. + xdg_toplevel_show_window_menu( + host->proxy, host_seat ? host_seat->proxy : NULL, serial, x, y); +} // NOLINT(whitespace/indent) + +static const struct xdg_toplevel_interface sl_xdg_toplevel_implementation = { + sl_xdg_toplevel_destroy, + ForwardRequest, + ForwardRequest, + ForwardRequest, + sl_xdg_toplevel_show_window_menu, + ForwardRequest, + ForwardRequest, + ForwardRequest, + ForwardRequest, + ForwardRequest, + ForwardRequest, + ForwardRequest, + ForwardRequest, + ForwardRequest, +}; + +static void sl_xdg_toplevel_configure(void* data, + struct xdg_toplevel* xdg_toplevel, + int32_t width, + int32_t height, + struct wl_array* states) { + struct sl_host_xdg_toplevel* host = static_cast( + xdg_toplevel_get_user_data(xdg_toplevel)); + + int32_t iwidth = width; + int32_t iheight = height; + + sl_transform_host_to_guest(host->ctx, get_host_surface(host->originator), + &iwidth, &iheight); + + xdg_toplevel_send_configure(host->resource, iwidth, iheight, states); +} + +static void sl_xdg_toplevel_close(void* data, + struct xdg_toplevel* xdg_toplevel) { + struct sl_host_xdg_toplevel* host = static_cast( + xdg_toplevel_get_user_data(xdg_toplevel)); + + xdg_toplevel_send_close(host->resource); +} + +static const struct xdg_toplevel_listener sl_xdg_toplevel_listener = { + sl_xdg_toplevel_configure, sl_xdg_toplevel_close}; + +static void sl_destroy_host_xdg_toplevel(struct wl_resource* resource) { + struct sl_host_xdg_toplevel* host = + static_cast(wl_resource_get_user_data(resource)); + + xdg_toplevel_destroy(host->proxy); + wl_resource_set_user_data(resource, NULL); + delete host; +} + +static void sl_xdg_surface_destroy(struct wl_client* client, + struct wl_resource* resource) { + wl_resource_destroy(resource); +} + +static void sl_xdg_surface_get_toplevel(struct wl_client* client, + struct wl_resource* resource, + uint32_t id) { + struct sl_host_xdg_surface* host = + static_cast(wl_resource_get_user_data(resource)); + struct sl_host_xdg_toplevel* host_xdg_toplevel = new sl_host_xdg_toplevel(); + + host_xdg_toplevel->ctx = host->ctx; + host_xdg_toplevel->resource = + wl_resource_create(client, &xdg_toplevel_interface, 1, id); + wl_resource_set_implementation( + host_xdg_toplevel->resource, &sl_xdg_toplevel_implementation, + host_xdg_toplevel, sl_destroy_host_xdg_toplevel); + host_xdg_toplevel->proxy = xdg_surface_get_toplevel(host->proxy); + host_xdg_toplevel->originator = host; + + xdg_toplevel_add_listener(host_xdg_toplevel->proxy, &sl_xdg_toplevel_listener, + host_xdg_toplevel); +} + +static void sl_xdg_surface_get_popup(struct wl_client* client, + struct wl_resource* resource, + uint32_t id, + struct wl_resource* parent_resource, + struct wl_resource* positioner_resource) { + struct sl_host_xdg_surface* host = + static_cast(wl_resource_get_user_data(resource)); + struct sl_host_xdg_surface* host_parent = + parent_resource ? static_cast( + wl_resource_get_user_data(parent_resource)) + : NULL; + struct sl_host_xdg_positioner* host_positioner = + static_cast( + wl_resource_get_user_data(positioner_resource)); + struct sl_host_xdg_popup* host_xdg_popup = new sl_host_xdg_popup(); + + host_xdg_popup->ctx = host->ctx; + host_xdg_popup->resource = + wl_resource_create(client, &xdg_popup_interface, 1, id); + wl_resource_set_implementation(host_xdg_popup->resource, + &sl_xdg_popup_implementation, host_xdg_popup, + sl_destroy_host_xdg_popup); + host_xdg_popup->proxy = xdg_surface_get_popup( + host->proxy, host_parent ? host_parent->proxy : NULL, + host_positioner->proxy); + host_xdg_popup->originator = host_parent; + + xdg_popup_add_listener(host_xdg_popup->proxy, &sl_xdg_popup_listener, + host_xdg_popup); +} // NOLINT(whitespace/indent) + +static void sl_xdg_surface_set_window_geometry(struct wl_client* client, + struct wl_resource* resource, + int32_t x, + int32_t y, + int32_t width, + int32_t height) { + struct sl_host_xdg_surface* host = + static_cast(wl_resource_get_user_data(resource)); + int32_t x1 = x; + int32_t y1 = y; + int32_t x2 = x + width; + int32_t y2 = y + height; + + sl_transform_guest_to_host(host->ctx, host->originator, &x1, &y1); + sl_transform_guest_to_host(host->ctx, host->originator, &x2, &y2); + + xdg_surface_set_window_geometry(host->proxy, x1, y1, x2 - x1, y2 - y1); +} + +static const struct xdg_surface_interface sl_xdg_surface_implementation = { + sl_xdg_surface_destroy, sl_xdg_surface_get_toplevel, + sl_xdg_surface_get_popup, sl_xdg_surface_set_window_geometry, + ForwardRequest}; + +static void sl_xdg_surface_configure(void* data, + struct xdg_surface* xdg_surface, + uint32_t serial) { + struct sl_host_xdg_surface* host = + static_cast(xdg_surface_get_user_data(xdg_surface)); + + xdg_surface_send_configure(host->resource, serial); +} + +static const struct xdg_surface_listener sl_xdg_surface_listener = { + sl_xdg_surface_configure}; + +static void sl_destroy_host_xdg_surface(struct wl_resource* resource) { + struct sl_host_xdg_surface* host = + static_cast(wl_resource_get_user_data(resource)); + + xdg_surface_destroy(host->proxy); + wl_resource_set_user_data(resource, NULL); + delete host; +} + +static void sl_xdg_shell_destroy(struct wl_client* client, + struct wl_resource* resource) { + wl_resource_destroy(resource); +} + +static void sl_xdg_shell_create_positioner(struct wl_client* client, + struct wl_resource* resource, + uint32_t id) { + struct sl_host_xdg_shell* host = + static_cast(wl_resource_get_user_data(resource)); + struct sl_host_xdg_positioner* host_xdg_positioner = + new sl_host_xdg_positioner(); + + host_xdg_positioner->ctx = host->ctx; + host_xdg_positioner->resource = + wl_resource_create(client, &xdg_positioner_interface, 1, id); + wl_resource_set_implementation( + host_xdg_positioner->resource, &sl_xdg_positioner_implementation, + host_xdg_positioner, sl_destroy_host_xdg_positioner); + host_xdg_positioner->proxy = xdg_wm_base_create_positioner(host->proxy); + xdg_positioner_set_user_data(host_xdg_positioner->proxy, host_xdg_positioner); +} + +static void sl_xdg_shell_get_xdg_surface(struct wl_client* client, + struct wl_resource* resource, + uint32_t id, + struct wl_resource* surface_resource) { + struct sl_host_xdg_shell* host = + static_cast(wl_resource_get_user_data(resource)); + struct sl_host_surface* host_surface = static_cast( + wl_resource_get_user_data(surface_resource)); + struct sl_host_xdg_surface* host_xdg_surface = new sl_host_xdg_surface(); + + host_xdg_surface->ctx = host->ctx; + host_xdg_surface->resource = + wl_resource_create(client, &xdg_surface_interface, 1, id); + wl_resource_set_implementation(host_xdg_surface->resource, + &sl_xdg_surface_implementation, + host_xdg_surface, sl_destroy_host_xdg_surface); + host_xdg_surface->proxy = + xdg_wm_base_get_xdg_surface(host->proxy, host_surface->proxy); + host_xdg_surface->originator = host_surface; + + xdg_surface_add_listener(host_xdg_surface->proxy, &sl_xdg_surface_listener, + host_xdg_surface); + host_surface->has_role = 1; +} + +static const struct xdg_wm_base_interface sl_xdg_shell_implementation = { + sl_xdg_shell_destroy, sl_xdg_shell_create_positioner, + sl_xdg_shell_get_xdg_surface, ForwardRequest}; + +static void sl_xdg_shell_ping(void* data, + struct xdg_wm_base* xdg_shell, + uint32_t serial) { + struct sl_host_xdg_shell* host = + static_cast(xdg_wm_base_get_user_data(xdg_shell)); + + xdg_wm_base_send_ping(host->resource, serial); +} + +static const struct xdg_wm_base_listener sl_xdg_shell_listener = { + sl_xdg_shell_ping}; + +static void sl_destroy_host_xdg_shell(struct wl_resource* resource) { + struct sl_host_xdg_shell* host = + static_cast(wl_resource_get_user_data(resource)); + + xdg_wm_base_destroy(host->proxy); + wl_resource_set_user_data(resource, NULL); + delete host; +} + +static void sl_bind_host_xdg_shell(struct wl_client* client, + void* data, + uint32_t version, + uint32_t id) { + struct sl_context* ctx = (struct sl_context*)data; + struct sl_host_xdg_shell* host = new sl_host_xdg_shell(); + host->ctx = ctx; + host->resource = wl_resource_create(client, &xdg_wm_base_interface, 1, id); + wl_resource_set_implementation(host->resource, &sl_xdg_shell_implementation, + host, sl_destroy_host_xdg_shell); + host->proxy = static_cast( + wl_registry_bind(wl_display_get_registry(ctx->display), + ctx->xdg_shell->id, &xdg_wm_base_interface, 1)); + xdg_wm_base_add_listener(host->proxy, &sl_xdg_shell_listener, host); +} + +struct sl_global* sl_xdg_shell_global_create(struct sl_context* ctx) { + return sl_global_create(ctx, &xdg_wm_base_interface, 1, ctx, + sl_bind_host_xdg_shell); +} diff --git a/sommelier/sommelier-xshape.cc b/sommelier/sommelier-xshape.cc new file mode 100644 index 0000000..7332e57 --- /dev/null +++ b/sommelier/sommelier-xshape.cc @@ -0,0 +1,192 @@ +// Copyright 2022 The ChromiumOS Authors. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include + +#include "sommelier.h" // NOLINT(build/include_directory) +#include "sommelier-tracing.h" // NOLINT(build/include_directory) +#include "sommelier-xshape.h" // NOLINT(build/include_directory) + +static void sl_clear_shape_region(sl_window* window) { + window->shaped = false; + pixman_region32_fini(&window->shape_rectangles); +} + +static void sl_attach_shape_region(struct sl_context* ctx, + xcb_window_t window) { + sl_window* sl_window = nullptr; + xcb_shape_get_rectangles_reply_t* reply; + int i; + + sl_window = sl_lookup_window(ctx, window); + if (!sl_window) + return; + + reply = xcb_shape_get_rectangles_reply( + ctx->connection, + xcb_shape_get_rectangles(ctx->connection, window, XCB_SHAPE_SK_BOUNDING), + NULL); + + if (!reply) + return; + + int nrects = xcb_shape_get_rectangles_rectangles_length(reply); + xcb_rectangle_t* rects = xcb_shape_get_rectangles_rectangles(reply); + + if (!rects || nrects <= 0) + return; + + pixman_box32_t* boxes = + static_cast(calloc(sizeof(pixman_box32_t), nrects)); + + if (!boxes) { + free(reply); + return; + } + + for (i = 0; i < nrects; i++) { + boxes[i].x1 = rects[i].x; + boxes[i].y1 = rects[i].y; + + boxes[i].x2 = rects[i].x + rects[i].width; + boxes[i].y2 = rects[i].y + rects[i].height; + } + + pixman_region32_init_rects(&sl_window->shape_rectangles, boxes, nrects); + + free(boxes); + free(reply); + sl_window->shaped = true; +} + +void sl_handle_shape_notify(struct sl_context* ctx, + struct xcb_shape_notify_event_t* event) { + sl_window* window = nullptr; + + window = sl_lookup_window(ctx, event->affected_window); + + if (!window) + return; + + sl_clear_shape_region(window); + + if (event->shaped) + sl_attach_shape_region(ctx, event->affected_window); + + return; +} + +void sl_shape_query(struct sl_context* ctx, xcb_window_t xwindow) { + xcb_shape_query_extents_reply_t* reply; + sl_window* sl_window = nullptr; + + sl_window = sl_lookup_window(ctx, xwindow); + if (!sl_window) + return; + + reply = xcb_shape_query_extents_reply( + ctx->connection, xcb_shape_query_extents(ctx->connection, xwindow), NULL); + + if (!reply) + return; + + sl_clear_shape_region(sl_window); + + if (reply->bounding_shaped) { + sl_attach_shape_region(ctx, xwindow); + } +} + +pixman_format_code_t sl_pixman_format_for_shm_format(uint32_t shm_format) { + pixman_format_code_t fmt = PIXMAN_a1; + + switch (shm_format) { + case WL_SHM_FORMAT_ARGB8888: + fmt = PIXMAN_a8r8g8b8; + break; + + case WL_SHM_FORMAT_XRGB8888: + fmt = PIXMAN_x8r8g8b8; + break; + + case WL_SHM_FORMAT_ABGR8888: + fmt = PIXMAN_a8b8g8r8; + break; + + case WL_SHM_FORMAT_XBGR8888: + fmt = PIXMAN_x8b8g8r8; + break; + + case WL_SHM_FORMAT_RGB565: + fmt = PIXMAN_r5g6b5; + break; + + default: + assert(0); + break; + } + + return fmt; +} + +void sl_xshape_generate_argb_image(struct sl_context* ctx, + pixman_region32_t* shape, + struct sl_mmap* src_mmap, + pixman_image_t* dst_image, + uint32_t src_shm_format) { + int buf_width, buf_height, nrects; + pixman_region32_t intersect_rects; + pixman_image_t* src; + + assert(ctx); + assert(shape); + assert(src_mmap); + assert(dst_image); + + buf_width = pixman_image_get_width(dst_image); + buf_height = pixman_image_get_height(dst_image); + + if (buf_width <= 0 || buf_height <= 0) + return; + + // Intersect with the pixmap bounds to ensure we do not perform + // any OOB accesses + // In addition, we can assume the dimensions of the dst_image is + // the same size as the input image + + pixman_region32_init(&intersect_rects); + pixman_region32_intersect_rect(&intersect_rects, shape, 0, 0, buf_width, + buf_height); + + // With the blank destination image, we will take the source image and the + // shape rectangles and generate the "stamped out" ARGB image. + // + // This is accomplished by clearing out the destination image to be + // completely transparent as a first step. Then for each rectangular + // region within the shape data, we will use pixman_image_composite to + // copy that portion of the image from the source to the ARGB stamp out + // buffer. + // + // pixman_image_composite is used as it will automatically perform pixel + // format conversion for us. + + src = pixman_image_create_bits_no_clear( + sl_pixman_format_for_shm_format(src_shm_format), buf_width, buf_height, + reinterpret_cast(src_mmap->addr), src_mmap->stride[0]); + + pixman_box32_t* rects = pixman_region32_rectangles(&intersect_rects, &nrects); + + pixman_color_t clear = {.red = 0, .green = 0, .blue = 0, .alpha = 0}; + pixman_box32_t dstbox = {.x1 = 0, .y1 = 0, .x2 = buf_width, .y2 = buf_height}; + + pixman_image_fill_boxes(PIXMAN_OP_SRC, dst_image, &clear, 1, &dstbox); + + for (int i = 0; i < nrects; i++) { + pixman_image_composite(PIXMAN_OP_SRC, src, NULL, dst_image, rects[i].x1, + rects[i].y1, 0, 0, rects[i].x1, rects[i].y1, + (rects[i].x2 - rects[i].x1), + (rects[i].y2 - rects[i].y1)); + } +} diff --git a/sommelier/sommelier-xshape.h b/sommelier/sommelier-xshape.h new file mode 100644 index 0000000..36336f3 --- /dev/null +++ b/sommelier/sommelier-xshape.h @@ -0,0 +1,23 @@ +// Copyright 2022 The ChromiumOS Authors. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef VM_TOOLS_SOMMELIER_SOMMELIER_XSHAPE_H_ +#define VM_TOOLS_SOMMELIER_SOMMELIER_XSHAPE_H_ + +#include +#include +#include "sommelier-ctx.h" // NOLINT(build/include_directory) + +void sl_handle_shape_notify(struct sl_context* ctx, + struct xcb_shape_notify_event_t* event); + +void sl_shape_query(struct sl_context* ctx, xcb_window_t xwindow); + +void sl_xshape_generate_argb_image(struct sl_context* ctx, + pixman_region32_t* shape, + struct sl_mmap* src_mmap, + pixman_image_t* dst_image, + uint32_t src_shm_format); + +#endif // VM_TOOLS_SOMMELIER_SOMMELIER_XSHAPE_H_ diff --git a/sommelier/sommelier.c b/sommelier/sommelier.cc similarity index 56% rename from sommelier/sommelier.c rename to sommelier/sommelier.cc index 532685f..d759934 100644 --- a/sommelier/sommelier.c +++ b/sommelier/sommelier.cc @@ -1,18 +1,22 @@ -// Copyright 2017 The Chromium OS Authors. All rights reserved. +// Copyright 2017 The ChromiumOS Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "sommelier.h" +#include "sommelier.h" // NOLINT(build/include_directory) +#include "sommelier-tracing.h" // NOLINT(build/include_directory) +#include "sommelier-transform.h" // NOLINT(build/include_directory) +#include "sommelier-xshape.h" // NOLINT(build/include_directory) #include #include #include #include #include -#include +#include #include #include #include +#include #include #include #include @@ -24,17 +28,26 @@ #include #include #include +#include +#include #include +#include -#include "aura-shell-client-protocol.h" -#include "drm-server-protocol.h" -#include "keyboard-extension-unstable-v1-client-protocol.h" -#include "linux-dmabuf-unstable-v1-client-protocol.h" -#include "pointer-constraints-unstable-v1-client-protocol.h" -#include "relative-pointer-unstable-v1-client-protocol.h" -#include "text-input-unstable-v1-client-protocol.h" -#include "viewporter-client-protocol.h" -#include "xdg-shell-unstable-v6-client-protocol.h" +#include "aura-shell-client-protocol.h" // NOLINT(build/include_directory) +#include "drm-server-protocol.h" // NOLINT(build/include_directory) +#ifdef GAMEPAD_SUPPORT +#include "gaming-input-unstable-v2-client-protocol.h" // NOLINT(build/include_directory) +#endif +#include "keyboard-extension-unstable-v1-client-protocol.h" // NOLINT(build/include_directory) +#include "linux-dmabuf-unstable-v1-client-protocol.h" // NOLINT(build/include_directory) +#include "linux-explicit-synchronization-unstable-v1-client-protocol.h" // NOLINT(build/include_directory) +#include "pointer-constraints-unstable-v1-client-protocol.h" // NOLINT(build/include_directory) +#include "relative-pointer-unstable-v1-client-protocol.h" // NOLINT(build/include_directory) +#include "text-input-extension-unstable-v1-client-protocol.h" // NOLINT(build/include_directory) +#include "text-input-unstable-v1-client-protocol.h" // NOLINT(build/include_directory) +#include "viewporter-client-protocol.h" // NOLINT(build/include_directory) +#include "xdg-output-unstable-v1-client-protocol.h" // NOLINT(build/include_directory) +#include "xdg-shell-client-protocol.h" // NOLINT(build/include_directory) // Check that required macro definitions exist. #ifndef XWAYLAND_PATH @@ -43,18 +56,6 @@ #ifndef XWAYLAND_GL_DRIVER_PATH #error XWAYLAND_GL_DRIVER_PATH must be defined #endif -#ifndef XWAYLAND_SHM_DRIVER -#error XWAYLAND_SHM_DRIVER must be defined -#endif -#ifndef SHM_DRIVER -#error SHM_DRIVER must be defined -#endif -#ifndef VIRTWL_DEVICE -#error VIRTWL_DEVICE must be defined -#endif -#ifndef PEER_CMD_PREFIX -#error PEER_CMD_PREFIX must be defined -#endif #ifndef FRAME_COLOR #error FRAME_COLOR must be defined #endif @@ -67,83 +68,6 @@ struct sl_data_source { struct wl_data_source* internal; }; -enum { - PROPERTY_WM_NAME, - PROPERTY_WM_CLASS, - PROPERTY_WM_TRANSIENT_FOR, - PROPERTY_WM_NORMAL_HINTS, - PROPERTY_WM_CLIENT_LEADER, - PROPERTY_MOTIF_WM_HINTS, - PROPERTY_NET_STARTUP_ID, - PROPERTY_NET_WM_STATE, - PROPERTY_GTK_THEME_VARIANT, -}; - -#define US_POSITION (1L << 0) -#define US_SIZE (1L << 1) -#define P_POSITION (1L << 2) -#define P_SIZE (1L << 3) -#define P_MIN_SIZE (1L << 4) -#define P_MAX_SIZE (1L << 5) -#define P_RESIZE_INC (1L << 6) -#define P_ASPECT (1L << 7) -#define P_BASE_SIZE (1L << 8) -#define P_WIN_GRAVITY (1L << 9) - -struct sl_wm_size_hints { - uint32_t flags; - int32_t x, y; - int32_t width, height; - int32_t min_width, min_height; - int32_t max_width, max_height; - int32_t width_inc, height_inc; - struct { - int32_t x; - int32_t y; - } min_aspect, max_aspect; - int32_t base_width, base_height; - int32_t win_gravity; -}; - -#define MWM_HINTS_FUNCTIONS (1L << 0) -#define MWM_HINTS_DECORATIONS (1L << 1) -#define MWM_HINTS_INPUT_MODE (1L << 2) -#define MWM_HINTS_STATUS (1L << 3) - -#define MWM_DECOR_ALL (1L << 0) -#define MWM_DECOR_BORDER (1L << 1) -#define MWM_DECOR_RESIZEH (1L << 2) -#define MWM_DECOR_TITLE (1L << 3) -#define MWM_DECOR_MENU (1L << 4) -#define MWM_DECOR_MINIMIZE (1L << 5) -#define MWM_DECOR_MAXIMIZE (1L << 6) - -struct sl_mwm_hints { - uint32_t flags; - uint32_t functions; - uint32_t decorations; - int32_t input_mode; - uint32_t status; -}; - -#define NET_WM_MOVERESIZE_SIZE_TOPLEFT 0 -#define NET_WM_MOVERESIZE_SIZE_TOP 1 -#define NET_WM_MOVERESIZE_SIZE_TOPRIGHT 2 -#define NET_WM_MOVERESIZE_SIZE_RIGHT 3 -#define NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT 4 -#define NET_WM_MOVERESIZE_SIZE_BOTTOM 5 -#define NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT 6 -#define NET_WM_MOVERESIZE_SIZE_LEFT 7 -#define NET_WM_MOVERESIZE_MOVE 8 - -#define NET_WM_STATE_REMOVE 0 -#define NET_WM_STATE_ADD 1 -#define NET_WM_STATE_TOGGLE 2 - -#define WM_STATE_WITHDRAWN 0 -#define WM_STATE_NORMAL 1 -#define WM_STATE_ICONIC 3 - #define SEND_EVENT_MASK 0x80 #define MIN_SCALE 0.1 @@ -161,81 +85,67 @@ struct sl_mwm_hints { #define LOCK_SUFFIX ".lock" #define LOCK_SUFFIXLEN 5 -#define APPLICATION_ID_FORMAT_PREFIX "org.chromium.termina" -#define XID_APPLICATION_ID_FORMAT APPLICATION_ID_FORMAT_PREFIX ".xid.%d" -#define WM_CLIENT_LEADER_APPLICATION_ID_FORMAT \ - APPLICATION_ID_FORMAT_PREFIX ".wmclientleader.%d" -#define WM_CLASS_APPLICATION_ID_FORMAT \ - APPLICATION_ID_FORMAT_PREFIX ".wmclass.%s" - #define MIN_AURA_SHELL_VERSION 6 +#define MAX_AURA_SHELL_VERSION 38 -// Performs an asprintf operation and checks the result for validity and calls -// abort() if there's a failure. Returns a newly allocated string rather than -// taking a double pointer argument like asprintf. -__attribute__((__format__(__printf__, 1, 0))) static char* sl_xasprintf( - const char* fmt, ...) { - char* str; - va_list args; - va_start(args, fmt); - int rv = vasprintf(&str, fmt, args); - assert(rv >= 0); - UNUSED(rv); - va_end(args); - return str; -} +int sl_open_wayland_socket(const char* socket_name, + struct sockaddr_un* addr, + int* lock_fd, + int* sock_fd); -struct sl_mmap* sl_mmap_create(int fd, - size_t size, - size_t bpp, - size_t num_planes, - size_t offset0, - size_t stride0, - size_t offset1, - size_t stride1, - size_t y_ss0, - size_t y_ss1) { - struct sl_mmap* map; - - map = malloc(sizeof(*map)); - map->refcount = 1; - map->fd = fd; - map->size = size; - map->num_planes = num_planes; - map->bpp = bpp; - map->offset[0] = offset0; - map->stride[0] = stride0; - map->offset[1] = offset1; - map->stride[1] = stride1; - map->y_ss[0] = y_ss0; - map->y_ss[1] = y_ss1; - map->begin_write = NULL; - map->end_write = NULL; - map->buffer_resource = NULL; - map->addr = - mmap(NULL, size + offset0, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - assert(map->addr != MAP_FAILED); - - return map; -} - -struct sl_mmap* sl_mmap_ref(struct sl_mmap* map) { - map->refcount++; - return map; -} - -void sl_mmap_unref(struct sl_mmap* map) { - if (map->refcount-- == 1) { - munmap(map->addr, map->size + map->offset[0]); - close(map->fd); - free(map); +int sl_shm_format_for_drm_format(uint32_t drm_format) { + switch (drm_format) { + case WL_DRM_FORMAT_NV12: + return WL_SHM_FORMAT_NV12; + case WL_DRM_FORMAT_RGB565: + return WL_SHM_FORMAT_RGB565; + case WL_DRM_FORMAT_ARGB8888: + return WL_SHM_FORMAT_ARGB8888; + case WL_DRM_FORMAT_ABGR8888: + return WL_SHM_FORMAT_ABGR8888; + case WL_DRM_FORMAT_XRGB8888: + return WL_SHM_FORMAT_XRGB8888; + case WL_DRM_FORMAT_XBGR8888: + return WL_SHM_FORMAT_XBGR8888; } + assert(0); + return 0; +} + +uint32_t sl_drm_format_for_shm_format(int format) { + switch (format) { + case WL_SHM_FORMAT_NV12: + return WL_DRM_FORMAT_NV12; + case WL_SHM_FORMAT_RGB565: + return WL_DRM_FORMAT_RGB565; + case WL_SHM_FORMAT_ARGB8888: + return WL_DRM_FORMAT_ARGB8888; + case WL_SHM_FORMAT_ABGR8888: + return WL_DRM_FORMAT_ABGR8888; + case WL_SHM_FORMAT_XRGB8888: + return WL_DRM_FORMAT_XRGB8888; + case WL_SHM_FORMAT_XBGR8888: + return WL_DRM_FORMAT_XBGR8888; + } + assert(0); + return 0; +} + +const char* net_wm_state_to_string(int i) { + switch (i) { + case NET_WM_STATE_REMOVE: + return "NET_WM_STATE_REMOVE"; + case NET_WM_STATE_ADD: + return "NET_WM_STATE_ADD"; + case NET_WM_STATE_TOGGLE: + return "NET_WM_STATE_TOGGLE"; + } + return ""; } struct sl_sync_point* sl_sync_point_create(int fd) { - struct sl_sync_point* sync_point; - - sync_point = malloc(sizeof(*sync_point)); + TRACE_EVENT("sync", "sl_sync_point_create"); + struct sl_sync_point* sync_point = new sl_sync_point(); sync_point->fd = fd; sync_point->sync = NULL; @@ -243,40 +153,24 @@ struct sl_sync_point* sl_sync_point_create(int fd) { } void sl_sync_point_destroy(struct sl_sync_point* sync_point) { + TRACE_EVENT("sync", "sl_sync_point_destroy"); close(sync_point->fd); - free(sync_point); + delete sync_point; } static void sl_internal_xdg_shell_ping(void* data, - struct zxdg_shell_v6* xdg_shell, + struct xdg_wm_base* xdg_shell, uint32_t serial) { - zxdg_shell_v6_pong(xdg_shell, serial); + TRACE_EVENT("shell", "sl_internal_xdg_shell_ping"); + xdg_wm_base_pong(xdg_shell, serial); } -static const struct zxdg_shell_v6_listener sl_internal_xdg_shell_listener = { +static const struct xdg_wm_base_listener sl_internal_xdg_shell_listener = { sl_internal_xdg_shell_ping}; -static void sl_send_configure_notify(struct sl_window* window) { - xcb_configure_notify_event_t event = { - .response_type = XCB_CONFIGURE_NOTIFY, - .event = window->id, - .window = window->id, - .above_sibling = XCB_WINDOW_NONE, - .x = window->x, - .y = window->y, - .width = window->width, - .height = window->height, - .border_width = window->border_width, - .override_redirect = 0, - .pad0 = 0, - .pad1 = 0, - }; - - xcb_send_event(window->ctx->connection, 0, window->id, - XCB_EVENT_MASK_STRUCTURE_NOTIFY, (char*)&event); -} - static void sl_adjust_window_size_for_screen_size(struct sl_window* window) { + TRACE_EVENT("surface", "sl_adjust_window_size_for_screen_size", "id", + window->id); struct sl_context* ctx = window->ctx; // Clamp size to screen. @@ -293,82 +187,24 @@ static void sl_adjust_window_position_for_screen_size( window->y = ctx->screen->height_in_pixels / 2 - window->height / 2; } -static void sl_configure_window(struct sl_window* window) { - assert(!window->pending_config.serial); - - if (window->next_config.mask) { - int values[5]; - int x = window->x; - int y = window->y; - int i = 0; - - xcb_configure_window(window->ctx->connection, window->frame_id, - window->next_config.mask, window->next_config.values); - - if (window->next_config.mask & XCB_CONFIG_WINDOW_X) - x = window->next_config.values[i++]; - if (window->next_config.mask & XCB_CONFIG_WINDOW_Y) - y = window->next_config.values[i++]; - if (window->next_config.mask & XCB_CONFIG_WINDOW_WIDTH) - window->width = window->next_config.values[i++]; - if (window->next_config.mask & XCB_CONFIG_WINDOW_HEIGHT) - window->height = window->next_config.values[i++]; - if (window->next_config.mask & XCB_CONFIG_WINDOW_BORDER_WIDTH) - window->border_width = window->next_config.values[i++]; - - // Set x/y to origin in case window gravity is not northwest as expected. - assert(window->managed); - values[0] = 0; - values[1] = 0; - values[2] = window->width; - values[3] = window->height; - values[4] = window->border_width; - xcb_configure_window( - window->ctx->connection, window->id, - XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_WIDTH | - XCB_CONFIG_WINDOW_HEIGHT | XCB_CONFIG_WINDOW_BORDER_WIDTH, - values); - - if (x != window->x || y != window->y) { - window->x = x; - window->y = y; - sl_send_configure_notify(window); - } - } - - if (window->managed) { - xcb_change_property(window->ctx->connection, XCB_PROP_MODE_REPLACE, - window->id, window->ctx->atoms[ATOM_NET_WM_STATE].value, - XCB_ATOM_ATOM, 32, window->next_config.states_length, - window->next_config.states); - } - - window->pending_config = window->next_config; - window->next_config.serial = 0; - window->next_config.mask = 0; - window->next_config.states_length = 0; -} - static void sl_set_input_focus(struct sl_context* ctx, struct sl_window* window) { if (window) { - xcb_client_message_event_t event = { - .response_type = XCB_CLIENT_MESSAGE, - .format = 32, - .window = window->id, - .type = ctx->atoms[ATOM_WM_PROTOCOLS].value, - .data.data32 = - { - ctx->atoms[ATOM_WM_TAKE_FOCUS].value, - XCB_CURRENT_TIME, - }, - }; + xcb_client_message_event_t event; + event.response_type = XCB_CLIENT_MESSAGE; + event.format = 32; + event.window = window->id; + event.type = ctx->atoms[ATOM_WM_PROTOCOLS].value; + event.data.data32[0] = ctx->atoms[ATOM_WM_TAKE_FOCUS].value; + event.data.data32[1] = XCB_CURRENT_TIME; if (!window->managed) return; - xcb_send_event(ctx->connection, 0, window->id, - XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, (char*)&event); + if (window->focus_model_take_focus) { + xcb_send_event(ctx->connection, 0, window->id, XCB_EVENT_MASK_NO_EVENT, + reinterpret_cast(&event)); + } xcb_set_input_focus(ctx->connection, XCB_INPUT_FOCUS_NONE, window->id, XCB_CURRENT_TIME); @@ -396,166 +232,14 @@ void sl_restack_windows(struct sl_context* ctx, uint32_t focus_resource_id) { } void sl_roundtrip(struct sl_context* ctx) { + TRACE_EVENT("other", "sl_roundtrip", "id", + ctx->application_id != nullptr ? ctx->application_id : ""); free(xcb_get_input_focus_reply(ctx->connection, xcb_get_input_focus(ctx->connection), NULL)); } -int sl_process_pending_configure_acks(struct sl_window* window, - struct sl_host_surface* host_surface) { - if (!window->pending_config.serial) - return 0; - - if (window->managed && host_surface) { - int width = window->width + window->border_width * 2; - int height = window->height + window->border_width * 2; - // Early out if we expect contents to match window size at some point in - // the future. - if (width != host_surface->contents_width || - height != host_surface->contents_height) { - return 0; - } - } - - if (window->xdg_surface) { - zxdg_surface_v6_ack_configure(window->xdg_surface, - window->pending_config.serial); - } - window->pending_config.serial = 0; - - if (window->next_config.serial) - sl_configure_window(window); - - return 1; -} - -static void sl_internal_xdg_surface_configure( - void* data, struct zxdg_surface_v6* xdg_surface, uint32_t serial) { - struct sl_window* window = zxdg_surface_v6_get_user_data(xdg_surface); - - window->next_config.serial = serial; - if (!window->pending_config.serial) { - struct wl_resource* host_resource; - struct sl_host_surface* host_surface = NULL; - - host_resource = - wl_client_get_object(window->ctx->client, window->host_surface_id); - if (host_resource) - host_surface = wl_resource_get_user_data(host_resource); - - sl_configure_window(window); - - if (sl_process_pending_configure_acks(window, host_surface)) { - if (host_surface) - wl_surface_commit(host_surface->proxy); - } - } -} - -static const struct zxdg_surface_v6_listener sl_internal_xdg_surface_listener = - {sl_internal_xdg_surface_configure}; - -static void sl_internal_xdg_toplevel_configure( - void* data, - struct zxdg_toplevel_v6* xdg_toplevel, - int32_t width, - int32_t height, - struct wl_array* states) { - struct sl_window* window = zxdg_toplevel_v6_get_user_data(xdg_toplevel); - int activated = 0; - uint32_t* state; - int i = 0; - - if (!window->managed) - return; - - if (width && height) { - int32_t width_in_pixels = width * window->ctx->scale; - int32_t height_in_pixels = height * window->ctx->scale; - int i = 0; - - window->next_config.mask = XCB_CONFIG_WINDOW_WIDTH | - XCB_CONFIG_WINDOW_HEIGHT | - XCB_CONFIG_WINDOW_BORDER_WIDTH; - if (!(window->size_flags & (US_POSITION | P_POSITION))) { - window->next_config.mask |= XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y; - window->next_config.values[i++] = - window->ctx->screen->width_in_pixels / 2 - width_in_pixels / 2; - window->next_config.values[i++] = - window->ctx->screen->height_in_pixels / 2 - height_in_pixels / 2; - } - window->next_config.values[i++] = width_in_pixels; - window->next_config.values[i++] = height_in_pixels; - window->next_config.values[i++] = 0; - } - - window->allow_resize = 1; - wl_array_for_each(state, states) { - if (*state == ZXDG_TOPLEVEL_V6_STATE_FULLSCREEN) { - window->allow_resize = 0; - window->next_config.states[i++] = - window->ctx->atoms[ATOM_NET_WM_STATE_FULLSCREEN].value; - } - if (*state == ZXDG_TOPLEVEL_V6_STATE_MAXIMIZED) { - window->allow_resize = 0; - window->next_config.states[i++] = - window->ctx->atoms[ATOM_NET_WM_STATE_MAXIMIZED_VERT].value; - window->next_config.states[i++] = - window->ctx->atoms[ATOM_NET_WM_STATE_MAXIMIZED_HORZ].value; - } - if (*state == ZXDG_TOPLEVEL_V6_STATE_ACTIVATED) - activated = 1; - if (*state == ZXDG_TOPLEVEL_V6_STATE_RESIZING) - window->allow_resize = 0; - } - - if (activated != window->activated) { - if (activated != (window->ctx->host_focus_window == window)) { - window->ctx->host_focus_window = activated ? window : NULL; - window->ctx->needs_set_input_focus = 1; - } - window->activated = activated; - } - - window->next_config.states_length = i; -} - -static void sl_internal_xdg_toplevel_close( - void* data, struct zxdg_toplevel_v6* xdg_toplevel) { - struct sl_window* window = zxdg_toplevel_v6_get_user_data(xdg_toplevel); - xcb_client_message_event_t event = { - .response_type = XCB_CLIENT_MESSAGE, - .format = 32, - .window = window->id, - .type = window->ctx->atoms[ATOM_WM_PROTOCOLS].value, - .data.data32 = - { - window->ctx->atoms[ATOM_WM_DELETE_WINDOW].value, - XCB_CURRENT_TIME, - }, - }; - - xcb_send_event(window->ctx->connection, 0, window->id, - XCB_EVENT_MASK_NO_EVENT, (const char*)&event); -} - -static const struct zxdg_toplevel_v6_listener - sl_internal_xdg_toplevel_listener = {sl_internal_xdg_toplevel_configure, - sl_internal_xdg_toplevel_close}; - -static void sl_internal_xdg_popup_configure(void* data, - struct zxdg_popup_v6* xdg_popup, - int32_t x, - int32_t y, - int32_t width, - int32_t height) {} - -static void sl_internal_xdg_popup_done(void* data, - struct zxdg_popup_v6* zxdg_popup_v6) {} - -static const struct zxdg_popup_v6_listener sl_internal_xdg_popup_listener = { - sl_internal_xdg_popup_configure, sl_internal_xdg_popup_done}; - static void sl_window_set_wm_state(struct sl_window* window, int state) { + TRACE_EVENT("surface", "sl_window_set_wm_state", "id", window->id); struct sl_context* ctx = window->ctx; uint32_t values[2]; @@ -567,231 +251,9 @@ static void sl_window_set_wm_state(struct sl_window* window, int state) { ctx->atoms[ATOM_WM_STATE].value, 32, 2, values); } -void sl_window_update(struct sl_window* window) { - struct wl_resource* host_resource = NULL; - struct sl_host_surface* host_surface; - struct sl_context* ctx = window->ctx; - struct sl_window* parent = NULL; - - if (window->host_surface_id) { - host_resource = wl_client_get_object(ctx->client, window->host_surface_id); - if (host_resource && window->unpaired) { - wl_list_remove(&window->link); - wl_list_insert(&ctx->windows, &window->link); - window->unpaired = 0; - } - } else if (!window->unpaired) { - wl_list_remove(&window->link); - wl_list_insert(&ctx->unpaired_windows, &window->link); - window->unpaired = 1; - } - - if (!host_resource) { - if (window->aura_surface) { - zaura_surface_destroy(window->aura_surface); - window->aura_surface = NULL; - } - if (window->xdg_toplevel) { - zxdg_toplevel_v6_destroy(window->xdg_toplevel); - window->xdg_toplevel = NULL; - } - if (window->xdg_popup) { - zxdg_popup_v6_destroy(window->xdg_popup); - window->xdg_popup = NULL; - } - if (window->xdg_surface) { - zxdg_surface_v6_destroy(window->xdg_surface); - window->xdg_surface = NULL; - } - window->realized = 0; - return; - } - - host_surface = wl_resource_get_user_data(host_resource); - assert(host_surface); - assert(!host_surface->has_role); - - assert(ctx->xdg_shell); - assert(ctx->xdg_shell->internal); - - if (window->managed) { - if (window->transient_for != XCB_WINDOW_NONE) { - struct sl_window* sibling; - - wl_list_for_each(sibling, &ctx->windows, link) { - if (sibling->id == window->transient_for) { - if (sibling->xdg_toplevel) - parent = sibling; - break; - } - } - } - } - - // If we have a transient parent, but could not find it in the list of - // realized windows, then pick the window that had the last event for the - // parent. We update this again when we gain focus, so if we picked the wrong - // one it can get corrected at that point (but it's also possible the parent - // will never be realized, which is why selecting one here is important). - if (!window->managed || - (!parent && window->transient_for != XCB_WINDOW_NONE)) { - struct sl_window* sibling; - uint32_t parent_last_event_serial = 0; - - wl_list_for_each(sibling, &ctx->windows, link) { - struct wl_resource* sibling_host_resource; - struct sl_host_surface* sibling_host_surface; - - if (!sibling->realized) - continue; - - sibling_host_resource = - wl_client_get_object(ctx->client, sibling->host_surface_id); - if (!sibling_host_resource) - continue; - - // Any parent will do but prefer last event window. - sibling_host_surface = wl_resource_get_user_data(sibling_host_resource); - if (parent_last_event_serial > sibling_host_surface->last_event_serial) - continue; - - // Do not use ourselves as the parent. - if (sibling->host_surface_id == window->host_surface_id) - continue; - - parent = sibling; - parent_last_event_serial = sibling_host_surface->last_event_serial; - } - } - - if (!window->depth) { - xcb_get_geometry_reply_t* geometry_reply = xcb_get_geometry_reply( - ctx->connection, xcb_get_geometry(ctx->connection, window->id), NULL); - if (geometry_reply) { - window->depth = geometry_reply->depth; - free(geometry_reply); - } - } - - if (!window->xdg_surface) { - window->xdg_surface = zxdg_shell_v6_get_xdg_surface( - ctx->xdg_shell->internal, host_surface->proxy); - zxdg_surface_v6_set_user_data(window->xdg_surface, window); - zxdg_surface_v6_add_listener(window->xdg_surface, - &sl_internal_xdg_surface_listener, window); - } - - if (ctx->aura_shell) { - uint32_t frame_color; - - if (!window->aura_surface) { - window->aura_surface = zaura_shell_get_aura_surface( - ctx->aura_shell->internal, host_surface->proxy); - } - - zaura_surface_set_frame(window->aura_surface, - window->decorated - ? ZAURA_SURFACE_FRAME_TYPE_NORMAL - : window->depth == 32 - ? ZAURA_SURFACE_FRAME_TYPE_NONE - : ZAURA_SURFACE_FRAME_TYPE_SHADOW); - - frame_color = window->dark_frame ? ctx->dark_frame_color : ctx->frame_color; - zaura_surface_set_frame_colors(window->aura_surface, frame_color, - frame_color); - zaura_surface_set_startup_id(window->aura_surface, window->startup_id); - - if (ctx->application_id) { - zaura_surface_set_application_id(window->aura_surface, - ctx->application_id); - } else { - // Don't set application id for X11 override redirect. This prevents - // aura shell from thinking that these are regular application windows - // that should appear in application lists. - if (!ctx->xwayland || window->managed) { - char* application_id_str; - if (window->clazz) { - application_id_str = - sl_xasprintf(WM_CLASS_APPLICATION_ID_FORMAT, window->clazz); - } else if (window->client_leader != XCB_WINDOW_NONE) { - application_id_str = sl_xasprintf( - WM_CLIENT_LEADER_APPLICATION_ID_FORMAT, window->client_leader); - } else { - application_id_str = - sl_xasprintf(XID_APPLICATION_ID_FORMAT, window->id); - } - - zaura_surface_set_application_id(window->aura_surface, - application_id_str); - free(application_id_str); - } - } - } - - // Always use top-level surface for X11 windows as we can't control when the - // window is closed. - if (ctx->xwayland || !parent) { - if (!window->xdg_toplevel) { - window->xdg_toplevel = zxdg_surface_v6_get_toplevel(window->xdg_surface); - zxdg_toplevel_v6_set_user_data(window->xdg_toplevel, window); - zxdg_toplevel_v6_add_listener(window->xdg_toplevel, - &sl_internal_xdg_toplevel_listener, window); - } - if (parent) - zxdg_toplevel_v6_set_parent(window->xdg_toplevel, parent->xdg_toplevel); - if (window->name) - zxdg_toplevel_v6_set_title(window->xdg_toplevel, window->name); - if (window->size_flags & P_MIN_SIZE) { - zxdg_toplevel_v6_set_min_size(window->xdg_toplevel, - window->min_width / ctx->scale, - window->min_height / ctx->scale); - } - if (window->size_flags & P_MAX_SIZE) { - zxdg_toplevel_v6_set_max_size(window->xdg_toplevel, - window->max_width / ctx->scale, - window->max_height / ctx->scale); - } - if (window->maximized) { - zxdg_toplevel_v6_set_maximized(window->xdg_toplevel); - } - } else if (!window->xdg_popup) { - struct zxdg_positioner_v6* positioner; - - positioner = zxdg_shell_v6_create_positioner(ctx->xdg_shell->internal); - assert(positioner); - zxdg_positioner_v6_set_anchor( - positioner, - ZXDG_POSITIONER_V6_ANCHOR_TOP | ZXDG_POSITIONER_V6_ANCHOR_LEFT); - zxdg_positioner_v6_set_gravity( - positioner, - ZXDG_POSITIONER_V6_GRAVITY_BOTTOM | ZXDG_POSITIONER_V6_GRAVITY_RIGHT); - zxdg_positioner_v6_set_anchor_rect( - positioner, (window->x - parent->x) / ctx->scale, - (window->y - parent->y) / ctx->scale, 1, 1); - - window->xdg_popup = zxdg_surface_v6_get_popup( - window->xdg_surface, parent->xdg_surface, positioner); - zxdg_popup_v6_set_user_data(window->xdg_popup, window); - zxdg_popup_v6_add_listener(window->xdg_popup, - &sl_internal_xdg_popup_listener, window); - - zxdg_positioner_v6_destroy(positioner); - } - - if ((window->size_flags & (US_POSITION | P_POSITION)) && parent && - ctx->aura_shell) { - zaura_surface_set_parent(window->aura_surface, parent->aura_surface, - (window->x - parent->x) / ctx->scale, - (window->y - parent->y) / ctx->scale); - } - - wl_surface_commit(host_surface->proxy); - if (host_surface->contents_width && host_surface->contents_height) - window->realized = 1; -} - static void sl_host_buffer_destroy(struct wl_client* client, struct wl_resource* resource) { + TRACE_EVENT("surface", "sl_host_buffer_destroy"); wl_resource_destroy(resource); } @@ -799,15 +261,24 @@ static const struct wl_buffer_interface sl_buffer_implementation = { sl_host_buffer_destroy}; static void sl_buffer_release(void* data, struct wl_buffer* buffer) { - struct sl_host_buffer* host = wl_buffer_get_user_data(buffer); + struct sl_host_buffer* host = + static_cast(wl_buffer_get_user_data(buffer)); + auto resource_id = host->resource ? wl_resource_get_id(host->resource) : -1; + TRACE_EVENT("surface", "sl_buffer_release", "resource_id", resource_id); + if (host->ctx->timing != NULL) { + host->ctx->timing->UpdateLastRelease(resource_id); + } wl_buffer_send_release(host->resource); } static const struct wl_buffer_listener sl_buffer_listener = {sl_buffer_release}; static void sl_destroy_host_buffer(struct wl_resource* resource) { - struct sl_host_buffer* host = wl_resource_get_user_data(resource); + TRACE_EVENT("surface", "sl_destroy_host_buffer", "resource_id", + resource ? wl_resource_get_id(resource) : -1); + struct sl_host_buffer* host = + static_cast(wl_resource_get_user_data(resource)); if (host->proxy) wl_buffer_destroy(host->proxy); @@ -819,19 +290,20 @@ static void sl_destroy_host_buffer(struct wl_resource* resource) { sl_sync_point_destroy(host->sync_point); } wl_resource_set_user_data(resource, NULL); - free(host); + delete host; } -struct sl_host_buffer* sl_create_host_buffer(struct wl_client* client, +struct sl_host_buffer* sl_create_host_buffer(struct sl_context* ctx, + struct wl_client* client, uint32_t id, struct wl_buffer* proxy, int32_t width, - int32_t height) { - struct sl_host_buffer* host_buffer; - - host_buffer = malloc(sizeof(*host_buffer)); - assert(host_buffer); + int32_t height, + bool is_drm) { + TRACE_EVENT("surface", "sl_create_host_buffer", "id", id); + struct sl_host_buffer* host_buffer = new sl_host_buffer(); + host_buffer->ctx = ctx; host_buffer->width = width; host_buffer->height = height; host_buffer->resource = @@ -843,24 +315,26 @@ struct sl_host_buffer* sl_create_host_buffer(struct wl_client* client, host_buffer->shm_format = 0; host_buffer->proxy = proxy; if (host_buffer->proxy) { - wl_buffer_set_user_data(host_buffer->proxy, host_buffer); wl_buffer_add_listener(host_buffer->proxy, &sl_buffer_listener, host_buffer); } host_buffer->sync_point = NULL; + host_buffer->is_drm = is_drm; return host_buffer; -} +} // NOLINT(whitespace/indent) static void sl_internal_data_offer_destroy(struct sl_data_offer* host) { + TRACE_EVENT("other", "sl_internal_data_offer_destroy"); wl_data_offer_destroy(host->internal); wl_array_release(&host->atoms); wl_array_release(&host->cookies); - free(host); + delete host; } static void sl_set_selection(struct sl_context* ctx, struct sl_data_offer* data_offer) { + TRACE_EVENT("other", "sl_set_selection"); if (ctx->selection_data_offer) { sl_internal_data_offer_destroy(ctx->selection_data_offer); ctx->selection_data_offer = NULL; @@ -877,15 +351,19 @@ static void sl_set_selection(struct sl_context* ctx, int atoms = data_offer->cookies.size / sizeof(xcb_intern_atom_cookie_t); wl_array_add(&data_offer->atoms, sizeof(xcb_atom_t) * (atoms + 2)); - ((xcb_atom_t*)data_offer->atoms.data)[0] = ctx->atoms[ATOM_TARGETS].value; - ((xcb_atom_t*)data_offer->atoms.data)[1] = ctx->atoms[ATOM_TIMESTAMP].value; + (reinterpret_cast(data_offer->atoms.data))[0] = + ctx->atoms[ATOM_TARGETS].value; + (reinterpret_cast(data_offer->atoms.data))[1] = + ctx->atoms[ATOM_TIMESTAMP].value; for (int i = 0; i < atoms; i++) { xcb_intern_atom_cookie_t cookie = - ((xcb_intern_atom_cookie_t*)data_offer->cookies.data)[i]; + (reinterpret_cast( + data_offer->cookies.data))[i]; xcb_intern_atom_reply_t* reply = xcb_intern_atom_reply(ctx->connection, cookie, NULL); if (reply) { - ((xcb_atom_t*)data_offer->atoms.data)[i + 2] = reply->atom; + (reinterpret_cast(data_offer->atoms.data))[i + 2] = + reply->atom; free(reply); } } @@ -900,18 +378,23 @@ static void sl_set_selection(struct sl_context* ctx, static void sl_internal_data_offer_offer(void* data, struct wl_data_offer* data_offer, const char* type) { - struct sl_data_offer* host = data; - xcb_intern_atom_cookie_t* cookie = - wl_array_add(&host->cookies, sizeof(xcb_intern_atom_cookie_t)); + TRACE_EVENT("other", "sl_internal_data_offer_offer"); + struct sl_data_offer* host = static_cast(data); + xcb_intern_atom_cookie_t* cookie = static_cast( + wl_array_add(&host->cookies, sizeof(xcb_intern_atom_cookie_t))); *cookie = xcb_intern_atom(host->ctx->connection, 0, strlen(type), type); } static void sl_internal_data_offer_source_actions( - void* data, struct wl_data_offer* data_offer, uint32_t source_actions) {} + void* data, struct wl_data_offer* data_offer, uint32_t source_actions) { + TRACE_EVENT("other", "sl_internal_data_offer_source_actions"); +} static void sl_internal_data_offer_action(void* data, struct wl_data_offer* data_offer, - uint32_t dnd_action) {} + uint32_t dnd_action) { + TRACE_EVENT("other", "sl_internal_data_offer_action"); +} static const struct wl_data_offer_listener sl_internal_data_offer_listener = { sl_internal_data_offer_offer, sl_internal_data_offer_source_actions, @@ -922,10 +405,7 @@ static void sl_internal_data_device_data_offer( struct wl_data_device* data_device, struct wl_data_offer* data_offer) { struct sl_context* ctx = (struct sl_context*)data; - struct sl_data_offer* host_data_offer; - - host_data_offer = malloc(sizeof(*host_data_offer)); - assert(host_data_offer); + struct sl_data_offer* host_data_offer = new sl_data_offer(); host_data_offer->ctx = ctx; host_data_offer->internal = data_offer; @@ -962,7 +442,9 @@ static void sl_internal_data_device_selection( struct wl_data_offer* data_offer) { struct sl_context* ctx = (struct sl_context*)data; struct sl_data_offer* host_data_offer = - data_offer ? wl_data_offer_get_user_data(data_offer) : NULL; + data_offer + ? static_cast(wl_data_offer_get_user_data(data_offer)) + : NULL; sl_set_selection(ctx, host_data_offer); } @@ -987,75 +469,67 @@ void sl_host_seat_added(struct sl_host_seat* host) { wl_data_device_add_listener(ctx->selection_data_device, &sl_internal_data_device_listener, ctx); } + +#ifdef GAMEPAD_SUPPORT + sl_gaming_seat_add_listener(ctx); +#endif } void sl_host_seat_removed(struct sl_host_seat* host) { + TRACE_EVENT("other", "sl_host_seat_removed"); if (host->seat->ctx->default_seat == host) host->seat->ctx->default_seat = NULL; } -struct sl_global* sl_global_create(struct sl_context* ctx, - const struct wl_interface* interface, - int version, - void* data, - wl_global_bind_func_t bind) { - struct sl_host_registry* registry; - struct sl_global* global; - - assert(version > 0); - assert(version <= interface->version); - - global = malloc(sizeof *global); - assert(global); - - global->ctx = ctx; - global->name = ctx->next_global_id++; - global->interface = interface; - global->version = version; - global->data = data; - global->bind = bind; - wl_list_insert(ctx->globals.prev, &global->link); - - wl_list_for_each(registry, &ctx->registries, link) { - wl_resource_post_event(registry->resource, WL_REGISTRY_GLOBAL, global->name, - global->interface->name, global->version); +bool sl_client_supports_interface(const sl_context* ctx, + const wl_client* client, + const wl_interface* interface) { + if (ctx->client == client) { + return true; } - - return global; + // Clients created for IME support only receive the minimal set of required + // interfaces. + const char* name = interface->name; + return strcmp(name, "wl_seat") == 0 || + strcmp(name, "zwp_text_input_manager_v1") == 0 || + strcmp(name, "zcr_text_input_extension_v1") == 0 || + strcmp(name, "zcr_text_input_x11_v1") == 0; } static void sl_global_destroy(struct sl_global* global) { + TRACE_EVENT("other", "sl_global_destroy"); struct sl_host_registry* registry; - wl_list_for_each(registry, &global->ctx->registries, link) + wl_list_for_each(registry, &global->ctx->registries, link) { + if (sl_client_supports_interface(global->ctx, + wl_resource_get_client(registry->resource), + global->interface)) { wl_resource_post_event(registry->resource, WL_REGISTRY_GLOBAL_REMOVE, global->name); + } + } + wl_list_remove(&global->link); free(global); } -static void sl_registry_handler(void* data, - struct wl_registry* registry, - uint32_t id, - const char* interface, - uint32_t version) { +// Called on each "wl_registry::global" event from the host compositor, +// giving Sommelier an opportunity to bind to the new global object +// (so we can receive events or invoke requests on it), and/or forward the +// wl_registry::global event on to our clients. +void sl_registry_handler(void* data, + struct wl_registry* registry, + uint32_t id, + const char* interface, + uint32_t version) { struct sl_context* ctx = (struct sl_context*)data; + TRACE_EVENT("other", "sl_registry_handler", "id", id); if (strcmp(interface, "wl_compositor") == 0) { - struct sl_compositor* compositor = malloc(sizeof(struct sl_compositor)); - assert(compositor); - compositor->ctx = ctx; - compositor->id = id; - assert(version >= 3); - compositor->version = 3; - compositor->internal = wl_registry_bind( - registry, id, &wl_compositor_interface, compositor->version); - assert(!ctx->compositor); - ctx->compositor = compositor; - compositor->host_global = sl_compositor_global_create(ctx); + sl_compositor_init_context(ctx, registry, id, version); } else if (strcmp(interface, "wl_subcompositor") == 0) { struct sl_subcompositor* subcompositor = - malloc(sizeof(struct sl_subcompositor)); + static_cast(malloc(sizeof(struct sl_subcompositor))); assert(subcompositor); subcompositor->ctx = ctx; subcompositor->id = id; @@ -1063,16 +537,18 @@ static void sl_registry_handler(void* data, ctx->subcompositor = subcompositor; subcompositor->host_global = sl_subcompositor_global_create(ctx); } else if (strcmp(interface, "wl_shm") == 0) { - struct sl_shm* shm = malloc(sizeof(struct sl_shm)); + struct sl_shm* shm = static_cast(malloc(sizeof(struct sl_shm))); assert(shm); shm->ctx = ctx; shm->id = id; - shm->internal = wl_registry_bind(registry, id, &wl_shm_interface, 1); + shm->internal = static_cast( + wl_registry_bind(registry, id, &wl_shm_interface, 1)); assert(!ctx->shm); ctx->shm = shm; shm->host_global = sl_shm_global_create(ctx); } else if (strcmp(interface, "wl_shell") == 0) { - struct sl_shell* shell = malloc(sizeof(struct sl_shell)); + struct sl_shell* shell = + static_cast(malloc(sizeof(struct sl_shell))); assert(shell); shell->ctx = ctx; shell->id = id; @@ -1080,15 +556,18 @@ static void sl_registry_handler(void* data, ctx->shell = shell; shell->host_global = sl_shell_global_create(ctx); } else if (strcmp(interface, "wl_output") == 0) { - struct sl_output* output = malloc(sizeof(struct sl_output)); + struct sl_output* output = + static_cast(malloc(sizeof(struct sl_output))); assert(output); output->ctx = ctx; output->id = id; output->version = MIN(3, version); output->host_global = sl_output_global_create(output); wl_list_insert(&ctx->outputs, &output->link); + output->host_output = NULL; } else if (strcmp(interface, "wl_seat") == 0) { - struct sl_seat* seat = malloc(sizeof(struct sl_seat)); + struct sl_seat* seat = + static_cast(malloc(sizeof(struct sl_seat))); assert(seat); seat->ctx = ctx; seat->id = id; @@ -1098,31 +577,30 @@ static void sl_registry_handler(void* data, wl_list_insert(&ctx->seats, &seat->link); } else if (strcmp(interface, "zwp_relative_pointer_manager_v1") == 0) { struct sl_relative_pointer_manager* relative_pointer = - malloc(sizeof(struct sl_relative_pointer_manager)); + static_cast( + malloc(sizeof(struct sl_relative_pointer_manager))); assert(relative_pointer); relative_pointer->ctx = ctx; relative_pointer->id = id; - relative_pointer->internal = wl_registry_bind( - registry, id, &zwp_relative_pointer_manager_v1_interface, 1); assert(!ctx->relative_pointer_manager); ctx->relative_pointer_manager = relative_pointer; relative_pointer->host_global = sl_relative_pointer_manager_global_create(ctx); } else if (strcmp(interface, "zwp_pointer_constraints_v1") == 0) { struct sl_pointer_constraints* pointer_constraints = - malloc(sizeof(struct sl_pointer_constraints)); + static_cast( + malloc(sizeof(struct sl_pointer_constraints))); assert(pointer_constraints); pointer_constraints->ctx = ctx; pointer_constraints->id = id; - pointer_constraints->internal = wl_registry_bind( - registry, id, &zwp_pointer_constraints_v1_interface, 1); assert(!ctx->pointer_constraints); ctx->pointer_constraints = pointer_constraints; pointer_constraints->host_global = sl_pointer_constraints_global_create(ctx); } else if (strcmp(interface, "wl_data_device_manager") == 0) { struct sl_data_device_manager* data_device_manager = - malloc(sizeof(struct sl_data_device_manager)); + static_cast( + malloc(sizeof(struct sl_data_device_manager))); assert(data_device_manager); data_device_manager->ctx = ctx; data_device_manager->id = id; @@ -1132,15 +610,16 @@ static void sl_registry_handler(void* data, assert(!ctx->data_device_manager); ctx->data_device_manager = data_device_manager; if (ctx->xwayland) { - data_device_manager->internal = + data_device_manager->internal = static_cast( wl_registry_bind(registry, id, &wl_data_device_manager_interface, - data_device_manager->version); + data_device_manager->version)); } else { data_device_manager->host_global = sl_data_device_manager_global_create(ctx); } - } else if (strcmp(interface, "zxdg_shell_v6") == 0) { - struct sl_xdg_shell* xdg_shell = malloc(sizeof(struct sl_xdg_shell)); + } else if (strcmp(interface, "xdg_wm_base") == 0) { + struct sl_xdg_shell* xdg_shell = + static_cast(malloc(sizeof(struct sl_xdg_shell))); assert(xdg_shell); xdg_shell->ctx = ctx; xdg_shell->id = id; @@ -1149,35 +628,37 @@ static void sl_registry_handler(void* data, assert(!ctx->xdg_shell); ctx->xdg_shell = xdg_shell; if (ctx->xwayland) { - xdg_shell->internal = - wl_registry_bind(registry, id, &zxdg_shell_v6_interface, 1); - zxdg_shell_v6_add_listener(xdg_shell->internal, - &sl_internal_xdg_shell_listener, NULL); + xdg_shell->internal = static_cast( + wl_registry_bind(registry, id, &xdg_wm_base_interface, 1)); + xdg_wm_base_add_listener(xdg_shell->internal, + &sl_internal_xdg_shell_listener, NULL); } else { xdg_shell->host_global = sl_xdg_shell_global_create(ctx); } } else if (strcmp(interface, "zaura_shell") == 0) { if (version >= MIN_AURA_SHELL_VERSION) { - struct sl_aura_shell* aura_shell = malloc(sizeof(struct sl_aura_shell)); + struct sl_aura_shell* aura_shell = + static_cast(malloc(sizeof(struct sl_aura_shell))); assert(aura_shell); aura_shell->ctx = ctx; aura_shell->id = id; - aura_shell->version = MIN(MIN_AURA_SHELL_VERSION, version); + aura_shell->version = MIN(MAX_AURA_SHELL_VERSION, version); aura_shell->host_gtk_shell_global = NULL; - aura_shell->internal = wl_registry_bind( - registry, id, &zaura_shell_interface, aura_shell->version); + aura_shell->internal = static_cast(wl_registry_bind( + registry, id, &zaura_shell_interface, aura_shell->version)); assert(!ctx->aura_shell); ctx->aura_shell = aura_shell; aura_shell->host_gtk_shell_global = sl_gtk_shell_global_create(ctx); } } else if (strcmp(interface, "wp_viewporter") == 0) { - struct sl_viewporter* viewporter = malloc(sizeof(struct sl_viewporter)); + struct sl_viewporter* viewporter = + static_cast(malloc(sizeof(struct sl_viewporter))); assert(viewporter); viewporter->ctx = ctx; viewporter->id = id; viewporter->host_viewporter_global = NULL; - viewporter->internal = - wl_registry_bind(registry, id, &wp_viewporter_interface, 1); + viewporter->internal = static_cast( + wl_registry_bind(registry, id, &wp_viewporter_interface, 1)); assert(!ctx->viewporter); ctx->viewporter = viewporter; viewporter->host_viewporter_global = sl_viewporter_global_create(ctx); @@ -1185,43 +666,99 @@ static void sl_registry_handler(void* data, ctx->scale = MIN(MAX_SCALE, MAX(MIN_SCALE, ctx->desired_scale)); } else if (strcmp(interface, "zwp_linux_dmabuf_v1") == 0) { struct sl_linux_dmabuf* linux_dmabuf = - malloc(sizeof(struct sl_linux_dmabuf)); + static_cast(malloc(sizeof(struct sl_linux_dmabuf))); assert(linux_dmabuf); linux_dmabuf->ctx = ctx; linux_dmabuf->id = id; linux_dmabuf->version = MIN(2, version); - linux_dmabuf->internal = wl_registry_bind( - registry, id, &zwp_linux_dmabuf_v1_interface, linux_dmabuf->version); + linux_dmabuf->internal = static_cast(wl_registry_bind( + registry, id, &zwp_linux_dmabuf_v1_interface, linux_dmabuf->version)); assert(!ctx->linux_dmabuf); ctx->linux_dmabuf = linux_dmabuf; linux_dmabuf->host_drm_global = sl_drm_global_create(ctx); + } else if (strcmp(interface, "zwp_linux_explicit_synchronization_v1") == 0) { + struct sl_linux_explicit_synchronization* linux_explicit_synchronization = + static_cast( + malloc(sizeof(struct sl_linux_explicit_synchronization))); + assert(linux_explicit_synchronization); + linux_explicit_synchronization->ctx = ctx; + linux_explicit_synchronization->id = id; + linux_explicit_synchronization->internal = + static_cast(wl_registry_bind( + registry, id, &zwp_linux_explicit_synchronization_v1_interface, 1)); + assert(!ctx->linux_explicit_synchronization); + ctx->linux_explicit_synchronization = linux_explicit_synchronization; } else if (strcmp(interface, "zcr_keyboard_extension_v1") == 0) { struct sl_keyboard_extension* keyboard_extension = - malloc(sizeof(struct sl_keyboard_extension)); + static_cast( + malloc(sizeof(struct sl_keyboard_extension))); assert(keyboard_extension); keyboard_extension->ctx = ctx; keyboard_extension->id = id; keyboard_extension->internal = - wl_registry_bind(registry, id, &zcr_keyboard_extension_v1_interface, 1); + static_cast(wl_registry_bind( + registry, id, &zcr_keyboard_extension_v1_interface, 1)); assert(!ctx->keyboard_extension); ctx->keyboard_extension = keyboard_extension; } else if (strcmp(interface, "zwp_text_input_manager_v1") == 0) { struct sl_text_input_manager* text_input_manager = - malloc(sizeof(struct sl_text_input_manager)); + static_cast( + malloc(sizeof(struct sl_text_input_manager))); assert(text_input_manager); text_input_manager->ctx = ctx; text_input_manager->id = id; - text_input_manager->internal = - wl_registry_bind(registry, id, &zwp_text_input_manager_v1_interface, 1); text_input_manager->host_global = sl_text_input_manager_global_create(ctx); + text_input_manager->host_x11_global = sl_text_input_x11_global_create(ctx); assert(!ctx->text_input_manager); ctx->text_input_manager = text_input_manager; + } else if (strcmp(interface, "zcr_text_input_extension_v1") == 0) { + struct sl_text_input_extension* text_input_extension = + static_cast( + malloc(sizeof(struct sl_text_input_extension))); + assert(text_input_extension); + text_input_extension->ctx = ctx; + text_input_extension->id = id; + text_input_extension->host_global = + sl_text_input_extension_global_create(ctx); + assert(!ctx->text_input_extension); + ctx->text_input_extension = text_input_extension; +#ifdef GAMEPAD_SUPPORT + } else if (strcmp(interface, "zcr_gaming_input_v2") == 0) { + struct sl_gaming_input_manager* gaming_input_manager = + static_cast( + malloc(sizeof(struct sl_gaming_input_manager))); + assert(gaming_input_manager); + gaming_input_manager->ctx = ctx; + gaming_input_manager->id = id; + gaming_input_manager->internal = static_cast( + wl_registry_bind(registry, id, &zcr_gaming_input_v2_interface, 2)); + assert(!ctx->gaming_input_manager); + ctx->gaming_input_manager = gaming_input_manager; +#endif + } else if (strcmp(interface, "zxdg_output_manager_v1") == 0 && + ctx->use_direct_scale) { + // This protocol cannot be bound unconditionally as doing so + // causes issues under Crostini. For this reason we will only + // bind it when we need to do so (direct mode enabled). + + struct sl_xdg_output_manager* output_manager = + static_cast( + malloc(sizeof(struct sl_xdg_output_manager))); + assert(output_manager); + + output_manager->ctx = ctx; + output_manager->id = id; + output_manager->internal = + static_cast(wl_registry_bind( + registry, id, &zxdg_output_manager_v1_interface, MIN(3, version))); + ctx->xdg_output_manager = output_manager; } } static void sl_registry_remover(void* data, struct wl_registry* registry, uint32_t id) { + TRACE_EVENT("other", "sl_registry_remover"); struct sl_context* ctx = (struct sl_context*)data; struct sl_output* output; struct sl_seat* seat; @@ -1265,7 +802,7 @@ static void sl_registry_remover(void* data, if (ctx->xdg_shell->host_global) sl_global_destroy(ctx->xdg_shell->host_global); if (ctx->xdg_shell->internal) - zxdg_shell_v6_destroy(ctx->xdg_shell->internal); + xdg_wm_base_destroy(ctx->xdg_shell->internal); free(ctx->xdg_shell); ctx->xdg_shell = NULL; return; @@ -1294,6 +831,14 @@ static void sl_registry_remover(void* data, ctx->linux_dmabuf = NULL; return; } + if (ctx->linux_explicit_synchronization && + ctx->linux_explicit_synchronization->id == id) { + zwp_linux_explicit_synchronization_v1_destroy( + ctx->linux_explicit_synchronization->internal); + free(ctx->linux_explicit_synchronization); + ctx->linux_explicit_synchronization = NULL; + return; + } if (ctx->keyboard_extension && ctx->keyboard_extension->id == id) { zcr_keyboard_extension_v1_destroy(ctx->keyboard_extension->internal); free(ctx->keyboard_extension); @@ -1306,6 +851,20 @@ static void sl_registry_remover(void* data, ctx->text_input_manager = NULL; return; } + if (ctx->text_input_extension && ctx->text_input_extension->id == id) { + sl_global_destroy(ctx->text_input_extension->host_global); + free(ctx->text_input_extension); + ctx->text_input_extension = NULL; + return; + } +#ifdef GAMEPAD_SUPPORT + if (ctx->gaming_input_manager && ctx->gaming_input_manager->id == id) { + zcr_gaming_input_v2_destroy(ctx->gaming_input_manager->internal); + free(ctx->gaming_input_manager); + ctx->gaming_input_manager = NULL; + return; + } +#endif if (ctx->relative_pointer_manager && ctx->relative_pointer_manager->id == id) { sl_global_destroy(ctx->relative_pointer_manager->host_global); @@ -1322,6 +881,8 @@ static void sl_registry_remover(void* data, wl_list_for_each(output, &ctx->outputs, link) { if (output->id == id) { sl_global_destroy(output->host_global); + if (output->host_output) + wl_resource_destroy(output->host_output->resource); wl_list_remove(&output->link); free(output); return; @@ -1340,10 +901,11 @@ static void sl_registry_remover(void* data, assert(0); } -static const struct wl_registry_listener sl_registry_listener = { - sl_registry_handler, sl_registry_remover}; +const struct wl_registry_listener sl_registry_listener = {sl_registry_handler, + sl_registry_remover}; static int sl_handle_event(int fd, uint32_t mask, void* data) { + TRACE_EVENT("other", "sl_handle_event"); struct sl_context* ctx = (struct sl_context*)data; int count = 0; @@ -1365,87 +927,41 @@ static int sl_handle_event(int fd, uint32_t mask, void* data) { return count; } -static void sl_create_window(struct sl_context* ctx, - xcb_window_t id, - int x, - int y, - int width, - int height, - int border_width) { - struct sl_window* window = malloc(sizeof(struct sl_window)); +void sl_create_window(struct sl_context* ctx, + xcb_window_t id, + int x, + int y, + int width, + int height, + int border_width) { + TRACE_EVENT("surface", "sl_create_window"); + sl_window* window = new sl_window(ctx, id, x, y, width, height, border_width); uint32_t values[1]; - assert(window); - window->ctx = ctx; - window->id = id; - window->frame_id = XCB_WINDOW_NONE; - window->host_surface_id = 0; - window->unpaired = 1; - window->x = x; - window->y = y; - window->width = width; - window->height = height; - window->border_width = border_width; - window->depth = 0; - window->managed = 0; - window->realized = 0; - window->activated = 0; - window->maximized = 0; - window->allow_resize = 1; - window->transient_for = XCB_WINDOW_NONE; - window->client_leader = XCB_WINDOW_NONE; - window->decorated = 0; - window->name = NULL; - window->clazz = NULL; - window->startup_id = NULL; - window->dark_frame = 0; - window->size_flags = P_POSITION; - window->min_width = 0; - window->min_height = 0; - window->max_width = 0; - window->max_height = 0; - window->xdg_surface = NULL; - window->xdg_toplevel = NULL; - window->xdg_popup = NULL; - window->aura_surface = NULL; - window->next_config.serial = 0; - window->next_config.mask = 0; - window->next_config.states_length = 0; - window->pending_config.serial = 0; - window->pending_config.mask = 0; - window->pending_config.states_length = 0; - wl_list_insert(&ctx->unpaired_windows, &window->link); values[0] = XCB_EVENT_MASK_PROPERTY_CHANGE | XCB_EVENT_MASK_FOCUS_CHANGE; xcb_change_window_attributes(ctx->connection, window->id, XCB_CW_EVENT_MASK, values); + + // Also enable shape events for this window to come in if the xshape + // flag has been enabled + if (ctx->enable_xshape) + xcb_shape_select_input(ctx->connection, id, 1); } static void sl_destroy_window(struct sl_window* window) { + TRACE_EVENT("surface", "sl_destroy_window"); if (window->frame_id != XCB_WINDOW_NONE) xcb_destroy_window(window->ctx->connection, window->frame_id); - if (window->ctx->host_focus_window == window) { - window->ctx->host_focus_window = NULL; - window->ctx->needs_set_input_focus = 1; - } - if (window->xdg_popup) - zxdg_popup_v6_destroy(window->xdg_popup); + xdg_popup_destroy(window->xdg_popup); if (window->xdg_toplevel) - zxdg_toplevel_v6_destroy(window->xdg_toplevel); + xdg_toplevel_destroy(window->xdg_toplevel); if (window->xdg_surface) - zxdg_surface_v6_destroy(window->xdg_surface); + xdg_surface_destroy(window->xdg_surface); if (window->aura_surface) zaura_surface_destroy(window->aura_surface); - if (window->name) - free(window->name); - if (window->clazz) - free(window->clazz); - if (window->startup_id) - free(window->startup_id); - - wl_list_remove(&window->link); - free(window); + delete window; } static int sl_is_window(struct sl_window* window, xcb_window_t id) { @@ -1460,8 +976,7 @@ static int sl_is_window(struct sl_window* window, xcb_window_t id) { return 0; } -static struct sl_window* sl_lookup_window(struct sl_context* ctx, - xcb_window_t id) { +struct sl_window* sl_lookup_window(struct sl_context* ctx, xcb_window_t id) { struct sl_window* window; wl_list_for_each(window, &ctx->windows, link) { @@ -1475,7 +990,7 @@ static struct sl_window* sl_lookup_window(struct sl_context* ctx, return NULL; } -static int sl_is_our_window(struct sl_context* ctx, xcb_window_t id) { +int sl_is_our_window(struct sl_context* ctx, xcb_window_t id) { const xcb_setup_t* setup = xcb_get_setup(ctx->connection); return (id & ~setup->resource_id_mask) == setup->resource_id_base; @@ -1490,8 +1005,8 @@ static void sl_handle_create_notify(struct sl_context* ctx, event->height, event->border_width); } -static void sl_handle_destroy_notify(struct sl_context* ctx, - xcb_destroy_notify_event_t* event) { +void sl_handle_destroy_notify(struct sl_context* ctx, + xcb_destroy_notify_event_t* event) { struct sl_window* window; if (sl_is_our_window(ctx, event->window)) @@ -1504,8 +1019,8 @@ static void sl_handle_destroy_notify(struct sl_context* ctx, sl_destroy_window(window); } -static void sl_handle_reparent_notify(struct sl_context* ctx, - xcb_reparent_notify_event_t* event) { +void sl_handle_reparent_notify(struct sl_context* ctx, + xcb_reparent_notify_event_t* event) { struct sl_window* window; if (event->parent == ctx->screen->root) { @@ -1544,42 +1059,74 @@ static void sl_handle_reparent_notify(struct sl_context* ctx, sl_destroy_window(window); } -static void sl_handle_map_request(struct sl_context* ctx, - xcb_map_request_event_t* event) { +static const char* sl_decode_wm_class(struct sl_window* window, + xcb_get_property_reply_t* reply) { + // WM_CLASS property contains two consecutive null-terminated strings. + // These specify the Instance and Class names. If a global app ID is + // not set then use Class name for app ID. + const char* value = static_cast(xcb_get_property_value(reply)); + int value_length = xcb_get_property_value_length(reply); + int instance_length = strnlen(value, value_length); + if (value_length > instance_length) { + window->clazz = strndup(value + instance_length + 1, + value_length - instance_length - 1); + return window->clazz; + } + return nullptr; +} + +static void sl_set_application_id_from_atom(struct sl_context* ctx, + struct sl_window* window, + xcb_get_property_reply_t* reply) { + if (reply->type == XCB_ATOM_CARDINAL) { + uint32_t value = *static_cast(xcb_get_property_value(reply)); + window->app_id_property = std::to_string(value); + } +} + +void sl_handle_map_request(struct sl_context* ctx, + xcb_map_request_event_t* event) { + TRACE_EVENT("shm", "sl_handle_map_request", [&](perfetto::EventContext p) { + perfetto_annotate_window(ctx, p, "window", event->window); + }); struct sl_window* window = sl_lookup_window(ctx, event->window); struct { int type; xcb_atom_t atom; } properties[] = { {PROPERTY_WM_NAME, XCB_ATOM_WM_NAME}, + {PROPERTY_NET_WM_NAME, ctx->atoms[ATOM_NET_WM_NAME].value}, {PROPERTY_WM_CLASS, XCB_ATOM_WM_CLASS}, {PROPERTY_WM_TRANSIENT_FOR, XCB_ATOM_WM_TRANSIENT_FOR}, {PROPERTY_WM_NORMAL_HINTS, XCB_ATOM_WM_NORMAL_HINTS}, {PROPERTY_WM_CLIENT_LEADER, ctx->atoms[ATOM_WM_CLIENT_LEADER].value}, + {PROPERTY_WM_PROTOCOLS, ctx->atoms[ATOM_WM_PROTOCOLS].value}, {PROPERTY_MOTIF_WM_HINTS, ctx->atoms[ATOM_MOTIF_WM_HINTS].value}, {PROPERTY_NET_STARTUP_ID, ctx->atoms[ATOM_NET_STARTUP_ID].value}, {PROPERTY_NET_WM_STATE, ctx->atoms[ATOM_NET_WM_STATE].value}, {PROPERTY_GTK_THEME_VARIANT, ctx->atoms[ATOM_GTK_THEME_VARIANT].value}, + {PROPERTY_XWAYLAND_RANDR_EMU_MONITOR_RECTS, + ctx->atoms[ATOM_XWAYLAND_RANDR_EMU_MONITOR_RECTS].value}, + {PROPERTY_SPECIFIED_FOR_APP_ID, ctx->application_id_property_atom}, }; xcb_get_geometry_cookie_t geometry_cookie; xcb_get_property_cookie_t property_cookies[ARRAY_SIZE(properties)]; struct sl_wm_size_hints size_hints = {0}; struct sl_mwm_hints mwm_hints = {0}; - xcb_atom_t* reply_atoms; - bool maximize_h = false, maximize_v = false; + bool maximize_h = false, maximize_v = false, fullscreen = false; uint32_t values[5]; - int i; if (!window) return; - assert(!sl_is_our_window(ctx, event->window)); + if (sl_is_our_window(ctx, event->window)) + return; window->managed = 1; if (window->frame_id == XCB_WINDOW_NONE) geometry_cookie = xcb_get_geometry(ctx->connection, window->id); - for (i = 0; i < ARRAY_SIZE(properties); ++i) { + for (unsigned i = 0; i < ARRAY_SIZE(properties); ++i) { property_cookies[i] = xcb_get_property(ctx->connection, 0, window->id, properties[i].atom, XCB_ATOM_ANY, 0, 2048); @@ -1610,7 +1157,7 @@ static void sl_handle_map_request(struct sl_context* ctx, window->size_flags = 0; window->dark_frame = 0; - for (i = 0; i < ARRAY_SIZE(properties); ++i) { + for (unsigned i = 0; i < ARRAY_SIZE(properties); ++i) { xcb_get_property_reply_t* reply = xcb_get_property_reply(ctx->connection, property_cookies[i], NULL); @@ -1622,69 +1169,152 @@ static void sl_handle_map_request(struct sl_context* ctx, continue; } + const char* value = nullptr; + int value_int = std::numeric_limits::max(); + xcb_atom_t* reply_atoms = nullptr; + switch (properties[i].type) { case PROPERTY_WM_NAME: - window->name = strndup(xcb_get_property_value(reply), - xcb_get_property_value_length(reply)); - break; - case PROPERTY_WM_CLASS: { - // WM_CLASS property contains two consecutive null-terminated strings. - // These specify the Instance and Class names. If a global app ID is - // not set then use Class name for app ID. - const char* value = xcb_get_property_value(reply); - int value_length = xcb_get_property_value_length(reply); - int instance_length = strnlen(value, value_length); - if (value_length > instance_length) { - window->clazz = strndup(value + instance_length + 1, - value_length - instance_length - 1); + // WM_NAME is only used for the window name if _NET_WM_NAME is not + // present or not yet processed, thus window->name is null. + if (!window->has_net_wm_name) { + window->name = + strndup(static_cast(xcb_get_property_value(reply)), + xcb_get_property_value_length(reply)); + value = window->name; } - } break; + break; + case PROPERTY_NET_WM_NAME: + // _NET_WM_NAME is the preferred window name property. Remove any + // previously set window name. + if (window->name) { + free(window->name); + window->name = nullptr; + } + window->has_net_wm_name = true; + window->name = + strndup(static_cast(xcb_get_property_value(reply)), + xcb_get_property_value_length(reply)); + value = window->name; + break; + case PROPERTY_WM_CLASS: + value = sl_decode_wm_class(window, reply); + if (!value) + value = ""; + break; case PROPERTY_WM_TRANSIENT_FOR: - if (xcb_get_property_value_length(reply) >= 4) - window->transient_for = *((uint32_t*)xcb_get_property_value(reply)); + if (xcb_get_property_value_length(reply) >= 4) { + window->transient_for = + *(reinterpret_cast(xcb_get_property_value(reply))); + value_int = window->transient_for; + } break; case PROPERTY_WM_NORMAL_HINTS: - if (xcb_get_property_value_length(reply) >= sizeof(size_hints)) + if (xcb_get_property_value_length(reply) >= + static_cast(sizeof(size_hints))) memcpy(&size_hints, xcb_get_property_value(reply), sizeof(size_hints)); break; case PROPERTY_WM_CLIENT_LEADER: - if (xcb_get_property_value_length(reply) >= 4) - window->client_leader = *((uint32_t*)xcb_get_property_value(reply)); + if (xcb_get_property_value_length(reply) >= 4) { + window->client_leader = + *(reinterpret_cast(xcb_get_property_value(reply))); + value_int = window->client_leader; + } + break; + case PROPERTY_WM_PROTOCOLS: + reply_atoms = static_cast(xcb_get_property_value(reply)); + for (unsigned j = 0; + j < xcb_get_property_value_length(reply) / sizeof(xcb_atom_t); + ++j) { + if (reply_atoms[j] == ctx->atoms[ATOM_WM_TAKE_FOCUS].value) { + window->focus_model_take_focus = 1; + value = "ATOM_WM_TAKE_FOCUS"; + } + } break; case PROPERTY_MOTIF_WM_HINTS: - if (xcb_get_property_value_length(reply) >= sizeof(mwm_hints)) + if (xcb_get_property_value_length(reply) >= + static_cast(sizeof(mwm_hints))) memcpy(&mwm_hints, xcb_get_property_value(reply), sizeof(mwm_hints)); break; case PROPERTY_NET_STARTUP_ID: - window->startup_id = strndup(xcb_get_property_value(reply), - xcb_get_property_value_length(reply)); + window->startup_id = + strndup(static_cast(xcb_get_property_value(reply)), + xcb_get_property_value_length(reply)); + value = window->startup_id; break; case PROPERTY_NET_WM_STATE: - reply_atoms = xcb_get_property_value(reply); - for (i = 0; - i < xcb_get_property_value_length(reply) / sizeof(xcb_atom_t); - ++i) { - if (reply_atoms[i] == + reply_atoms = static_cast(xcb_get_property_value(reply)); + for (unsigned j = 0; + j < xcb_get_property_value_length(reply) / sizeof(xcb_atom_t); + ++j) { + if (reply_atoms[j] == ctx->atoms[ATOM_NET_WM_STATE_MAXIMIZED_HORZ].value) { maximize_h = true; - } else if (reply_atoms[i] == + } else if (reply_atoms[j] == ctx->atoms[ATOM_NET_WM_STATE_MAXIMIZED_VERT].value) { maximize_v = true; + } else if (reply_atoms[j] == + ctx->atoms[ATOM_NET_WM_STATE_FULLSCREEN].value) { + fullscreen = true; } } // Neither wayland not CrOS support 1D maximizing, so sommelier will // only consider a window maximized if both dimensions are. This // behaviour is consistent with sl_handle_client_message(). window->maximized = maximize_h && maximize_v; + window->fullscreen = fullscreen; + if (window->maximized) { + if (window->fullscreen) { + value = + "_NET_WM_STATE_FULLSCREEN, _NET_WM_STATE_MAXIMIZED_VERT && " + "HORZ"; + } else { + value = "_NET_WM_STATE_MAXIMIZED_VERT && HORZ"; + } + } else if (window->fullscreen) { + value = "_NET_WM_STATE_FULLSCREEN"; + } break; case PROPERTY_GTK_THEME_VARIANT: if (xcb_get_property_value_length(reply) >= 4) - window->dark_frame = !strcmp(xcb_get_property_value(reply), "dark"); + window->dark_frame = !strcmp( + static_cast(xcb_get_property_value(reply)), "dark"); + break; + case PROPERTY_SPECIFIED_FOR_APP_ID: + sl_set_application_id_from_atom(ctx, window, reply); + value = window->app_id_property.c_str(); break; default: break; } + + TRACE_EVENT("x11wm", "XCB_MAP_REQUEST: X property", + [&](perfetto::EventContext p) { + perfetto_annotate_atom(ctx, p, "name", properties[i].atom); + + if (value_int != std::numeric_limits::max()) { + auto* dbg = p.event()->add_debug_annotations(); + dbg->set_name("int value"); + dbg->set_int_value(value_int); + } + if (value) { + auto* dbg = p.event()->add_debug_annotations(); + dbg->set_name("str value"); + dbg->set_string_value(value, strlen(value)); + } + + switch (properties[i].type) { + case PROPERTY_WM_NORMAL_HINTS: + perfetto_annotate_size_hints(p, size_hints); + break; + case PROPERTY_XWAYLAND_RANDR_EMU_MONITOR_RECTS: + perfetto_annotate_cardinal_list(p, "value", reply); + break; + } + }); + free(reply); } @@ -1709,8 +1339,9 @@ static void sl_handle_map_request(struct sl_context* ctx, NULL); if (reply) { if (reply->type != XCB_ATOM_NONE) { - window->startup_id = strndup(xcb_get_property_value(reply), - xcb_get_property_value_length(reply)); + window->startup_id = + strndup(static_cast(xcb_get_property_value(reply)), + xcb_get_property_value_length(reply)); } free(reply); } @@ -1795,8 +1426,8 @@ static void sl_handle_map_request(struct sl_context* ctx, static void sl_handle_map_notify(struct sl_context* ctx, xcb_map_notify_event_t* event) {} -static void sl_handle_unmap_notify(struct sl_context* ctx, - xcb_unmap_notify_event_t* event) { +void sl_handle_unmap_notify(struct sl_context* ctx, + xcb_unmap_notify_event_t* event) { struct sl_window* window; if (sl_is_our_window(ctx, event->window)) @@ -1836,14 +1467,17 @@ static void sl_handle_unmap_notify(struct sl_context* ctx, window->size_flags = P_POSITION; } -static void sl_handle_configure_request(struct sl_context* ctx, - xcb_configure_request_event_t* event) { +void sl_handle_configure_request(struct sl_context* ctx, + xcb_configure_request_event_t* event) { struct sl_window* window = sl_lookup_window(ctx, event->window); + if (!window) + return; int width = window->width; int height = window->height; uint32_t values[7]; - assert(!sl_is_our_window(ctx, event->window)); + if (sl_is_our_window(ctx, event->window)) + return; if (!window->managed) { int i = 0; @@ -1872,15 +1506,15 @@ static void sl_handle_configure_request(struct sl_context* ctx, // that matching contents will arrive. if (window->xdg_toplevel) { if (window->pending_config.serial) { - zxdg_surface_v6_ack_configure(window->xdg_surface, - window->pending_config.serial); + xdg_surface_ack_configure(window->xdg_surface, + window->pending_config.serial); window->pending_config.serial = 0; window->pending_config.mask = 0; window->pending_config.states_length = 0; } if (window->next_config.serial) { - zxdg_surface_v6_ack_configure(window->xdg_surface, - window->next_config.serial); + xdg_surface_ack_configure(window->xdg_surface, + window->next_config.serial); window->next_config.serial = 0; window->next_config.mask = 0; window->next_config.states_length = 0; @@ -2001,28 +1635,45 @@ static void sl_handle_configure_notify(struct sl_context* ctx, static uint32_t sl_resize_edge(int net_wm_moveresize_size) { switch (net_wm_moveresize_size) { case NET_WM_MOVERESIZE_SIZE_TOPLEFT: - return ZXDG_TOPLEVEL_V6_RESIZE_EDGE_TOP_LEFT; + return XDG_TOPLEVEL_RESIZE_EDGE_TOP_LEFT; case NET_WM_MOVERESIZE_SIZE_TOP: - return ZXDG_TOPLEVEL_V6_RESIZE_EDGE_TOP; + return XDG_TOPLEVEL_RESIZE_EDGE_TOP; case NET_WM_MOVERESIZE_SIZE_TOPRIGHT: - return ZXDG_TOPLEVEL_V6_RESIZE_EDGE_TOP_RIGHT; + return XDG_TOPLEVEL_RESIZE_EDGE_TOP_RIGHT; case NET_WM_MOVERESIZE_SIZE_RIGHT: - return ZXDG_TOPLEVEL_V6_RESIZE_EDGE_RIGHT; + return XDG_TOPLEVEL_RESIZE_EDGE_RIGHT; case NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT: - return ZXDG_TOPLEVEL_V6_RESIZE_EDGE_BOTTOM_RIGHT; + return XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT; case NET_WM_MOVERESIZE_SIZE_BOTTOM: - return ZXDG_TOPLEVEL_V6_RESIZE_EDGE_BOTTOM; + return XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM; case NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT: - return ZXDG_TOPLEVEL_V6_RESIZE_EDGE_BOTTOM_LEFT; + return XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_LEFT; case NET_WM_MOVERESIZE_SIZE_LEFT: - return ZXDG_TOPLEVEL_V6_RESIZE_EDGE_LEFT; + return XDG_TOPLEVEL_RESIZE_EDGE_LEFT; default: - return ZXDG_TOPLEVEL_V6_RESIZE_EDGE_NONE; + return XDG_TOPLEVEL_RESIZE_EDGE_NONE; } } -static void sl_handle_client_message(struct sl_context* ctx, - xcb_client_message_event_t* event) { +static void sl_request_attention(struct sl_context* ctx, + struct sl_window* window, + bool is_strong_request) { + if (!window->aura_surface || + ctx->aura_shell->version < ZAURA_SURFACE_DRAW_ATTENTION_SINCE_VERSION) + return; + if (is_strong_request) { + zaura_surface_activate(window->aura_surface); + } else { + zaura_surface_draw_attention(window->aura_surface); + } +} + +void sl_handle_client_message(struct sl_context* ctx, + xcb_client_message_event_t* event) { + TRACE_EVENT("x11wm", "XCB_CLIENT_MESSAGE", [&](perfetto::EventContext p) { + perfetto_annotate_atom(ctx, p, "event->type", event->type); + perfetto_annotate_window(ctx, p, "event->window", event->window); + }); if (event->type == ctx->atoms[ATOM_WL_SURFACE_ID].value) { struct sl_window *window, *unpaired_window = NULL; @@ -2037,6 +1688,10 @@ static void sl_handle_client_message(struct sl_context* ctx, unpaired_window->host_surface_id = event->data.data32[0]; sl_window_update(unpaired_window); } + } else if (event->type == ctx->atoms[ATOM_NET_ACTIVE_WINDOW].value) { + struct sl_window* window = sl_lookup_window(ctx, event->window); + if (window) + sl_request_attention(ctx, window, /*is_strong_request=*/true); } else if (event->type == ctx->atoms[ATOM_NET_WM_MOVERESIZE].value) { struct sl_window* window = sl_lookup_window(ctx, event->window); @@ -2047,25 +1702,25 @@ static void sl_handle_client_message(struct sl_context* ctx, return; if (event->data.data32[2] == NET_WM_MOVERESIZE_MOVE) { - zxdg_toplevel_v6_move(window->xdg_toplevel, seat->proxy, - seat->seat->last_serial); + xdg_toplevel_move(window->xdg_toplevel, seat->proxy, + seat->seat->last_serial); } else { uint32_t edge = sl_resize_edge(event->data.data32[2]); - if (edge == ZXDG_TOPLEVEL_V6_RESIZE_EDGE_NONE) + if (edge == XDG_TOPLEVEL_RESIZE_EDGE_NONE) return; - zxdg_toplevel_v6_resize(window->xdg_toplevel, seat->proxy, - seat->seat->last_serial, edge); + xdg_toplevel_resize(window->xdg_toplevel, seat->proxy, + seat->seat->last_serial, edge); } } } else if (event->type == ctx->atoms[ATOM_NET_WM_STATE].value) { struct sl_window* window = sl_lookup_window(ctx, event->window); - if (window && window->xdg_toplevel) { + if (window) { int changed[ATOM_LAST + 1]; uint32_t action = event->data.data32[0]; - int i; + unsigned i; for (i = 0; i < ARRAY_SIZE(ctx->atoms); ++i) { changed[i] = event->data.data32[1] == ctx->atoms[i].value || @@ -2073,38 +1728,131 @@ static void sl_handle_client_message(struct sl_context* ctx, } if (changed[ATOM_NET_WM_STATE_FULLSCREEN]) { - if (action == NET_WM_STATE_ADD) - zxdg_toplevel_v6_set_fullscreen(window->xdg_toplevel, NULL); - else if (action == NET_WM_STATE_REMOVE) - zxdg_toplevel_v6_unset_fullscreen(window->xdg_toplevel); + TRACE_EVENT("x11wm", "XCB_CLIENT_MESSAGE: ATOM_NET_WM_STATE_FULLSCREEN", + "action", net_wm_state_to_string(action), "window->name", + window->name); + if (action == NET_WM_STATE_ADD) { + window->fullscreen = 1; + if (window->xdg_toplevel && !window->iconified) { + xdg_toplevel_set_fullscreen(window->xdg_toplevel, NULL); + } else { + window->pending_fullscreen_change = true; + } + } else if (action == NET_WM_STATE_REMOVE) { + window->fullscreen = 0; + if (window->xdg_toplevel && !window->iconified) { + xdg_toplevel_unset_fullscreen(window->xdg_toplevel); + } else { + window->pending_fullscreen_change = true; + } + } } if (changed[ATOM_NET_WM_STATE_MAXIMIZED_VERT] && changed[ATOM_NET_WM_STATE_MAXIMIZED_HORZ]) { - if (action == NET_WM_STATE_ADD) - zxdg_toplevel_v6_set_maximized(window->xdg_toplevel); - else if (action == NET_WM_STATE_REMOVE) - zxdg_toplevel_v6_unset_maximized(window->xdg_toplevel); + TRACE_EVENT( + "x11wm", + "XCB_CLIENT_MESSAGE: ATOM_NET_WM_STATE_MAXIMIZED_VERT && HORZ", + "action", net_wm_state_to_string(action), "window->name", + window->name); + if (action == NET_WM_STATE_ADD) { + window->maximized = 1; + if (window->xdg_toplevel && !window->iconified) { + xdg_toplevel_set_maximized(window->xdg_toplevel); + } else { + window->pending_maximized_change = true; + } + } else if (action == NET_WM_STATE_REMOVE) { + window->maximized = 0; + if (window->xdg_toplevel && !window->iconified) { + xdg_toplevel_unset_maximized(window->xdg_toplevel); + } else { + window->pending_maximized_change = true; + } + } } } } else if (event->type == ctx->atoms[ATOM_WM_CHANGE_STATE].value && event->data.data32[0] == WM_STATE_ICONIC) { struct sl_window* window = sl_lookup_window(ctx, event->window); + TRACE_EVENT("x11wm", "XCB_CLIENT_MESSAGE: WM_STATE_ICONIC (minimize)", + "window->name", window ? window->name : ""); if (window && window->xdg_toplevel) { - zxdg_toplevel_v6_set_minimized(window->xdg_toplevel); + xdg_toplevel_set_minimized(window->xdg_toplevel); +#ifdef BLACK_SCREEN_FIX + // Workaround for some borealis apps showing a black screen after losing + // focus from fullscreen. + // When a window is iconified, it should be unmapped. To return it back + // to a visible state, it should be remapped. However sommelier does not + // do this. Therefore we are sending a synthetic unmap then map notify + // so that the app is rendered again. + xcb_unmap_notify_event_t unmap_event = {.response_type = XCB_UNMAP_NOTIFY, + .pad0 = 0, + .event = window->id, + .window = window->id, + .from_configure = 0}; + + xcb_send_event(ctx->connection, 0, window->id, + XCB_EVENT_MASK_STRUCTURE_NOTIFY, + reinterpret_cast(&unmap_event)); + sl_send_configure_notify(window); + + sl_window_set_wm_state(window, WM_STATE_ICONIC); + sl_send_configure_notify(window); + + xcb_map_notify_event_t map_event = {.response_type = XCB_MAP_NOTIFY, + .pad0 = 0, + .event = window->id, + .window = window->id, + .override_redirect = 0}; + + xcb_send_event(ctx->connection, 0, window->id, + XCB_EVENT_MASK_STRUCTURE_NOTIFY, + reinterpret_cast(&map_event)); + sl_send_configure_notify(window); + + sl_window_set_wm_state(window, WM_STATE_NORMAL); + sl_send_configure_notify(window); + + sl_set_input_focus(ctx, nullptr); + xcb_flush(ctx->connection); + + // When we are iconified we want to suppress any calls that deiconify + // the window as it should in theory be unmapped. + window->iconified = 1; +#endif } } } -static void sl_handle_focus_in(struct sl_context* ctx, - xcb_focus_in_event_t* event) { +void sl_handle_focus_in(struct sl_context* ctx, xcb_focus_in_event_t* event) { struct sl_window* window = sl_lookup_window(ctx, event->event); if (window && window->transient_for != XCB_WINDOW_NONE) { // Set our parent now as it might not have been set properly when the // window was realized. struct sl_window* parent = sl_lookup_window(ctx, window->transient_for); if (parent && parent->xdg_toplevel && window->xdg_toplevel) - zxdg_toplevel_v6_set_parent(window->xdg_toplevel, parent->xdg_toplevel); + xdg_toplevel_set_parent(window->xdg_toplevel, parent->xdg_toplevel); + } + if (window) { + if (window->xdg_toplevel && window->pending_fullscreen_change) { + if (window->fullscreen) { + xdg_toplevel_set_fullscreen(window->xdg_toplevel, NULL); + } else { + xdg_toplevel_unset_fullscreen(window->xdg_toplevel); + } + + window->pending_fullscreen_change = false; + } + if (window->xdg_toplevel && window->pending_maximized_change) { + if (window->maximized) { + xdg_toplevel_set_maximized(window->xdg_toplevel); + } else { + xdg_toplevel_unset_maximized(window->xdg_toplevel); + } + window->pending_maximized_change = false; + } + window->iconified = 0; } } @@ -2131,8 +1879,7 @@ int sl_begin_data_source_send(struct sl_context* ctx, flags = fcntl(fd, F_GETFL, 0); rv = fcntl(fd, F_SETFL, flags | O_NONBLOCK); - assert(!rv); - UNUSED(rv); + errno_assert(!rv); ctx->selection_data_source_send_fd = fd; free(reply); @@ -2155,11 +1902,11 @@ void sl_process_data_source_send_pending_list(struct sl_context* ctx) { } static int sl_handle_selection_fd_writable(int fd, uint32_t mask, void* data) { - struct sl_context* ctx = data; - uint8_t* value; + struct sl_context* ctx = static_cast(data); int bytes, bytes_left; - value = xcb_get_property_value(ctx->selection_property_reply); + uint8_t* value = static_cast( + xcb_get_property_value(ctx->selection_property_reply)); bytes_left = xcb_get_property_value_length(ctx->selection_property_reply) - ctx->selection_property_offset; @@ -2184,8 +1931,7 @@ static int sl_handle_selection_fd_writable(int fd, uint32_t mask, void* data) { free(ctx->selection_property_reply); ctx->selection_property_reply = NULL; if (ctx->selection_send_event_source) { - wl_event_source_remove(ctx->selection_send_event_source); - ctx->selection_send_event_source = NULL; + ctx->selection_send_event_source.reset(); } if (fd < 0) { ctx->selection_data_source_send_fd = -1; @@ -2205,26 +1951,27 @@ static void sl_write_selection_property(struct sl_context* ctx, return; assert(!ctx->selection_send_event_source); - ctx->selection_send_event_source = wl_event_loop_add_fd( + ctx->selection_send_event_source.reset(wl_event_loop_add_fd( wl_display_get_event_loop(ctx->host_display), ctx->selection_data_source_send_fd, WL_EVENT_WRITABLE, - sl_handle_selection_fd_writable, ctx); + sl_handle_selection_fd_writable, ctx)); } static void sl_send_selection_notify(struct sl_context* ctx, xcb_atom_t property) { + TRACE_EVENT("other", "sl_send_selection_notify"); xcb_selection_notify_event_t event = { .response_type = XCB_SELECTION_NOTIFY, + .pad0 = 0, .sequence = 0, .time = ctx->selection_request.time, .requestor = ctx->selection_request.requestor, .selection = ctx->selection_request.selection, .target = ctx->selection_request.target, - .property = property, - .pad0 = 0}; + .property = property}; xcb_send_event(ctx->connection, 0, ctx->selection_request.requestor, - XCB_EVENT_MASK_NO_EVENT, (char*)&event); + XCB_EVENT_MASK_NO_EVENT, reinterpret_cast(&event)); } static void sl_send_selection_data(struct sl_context* ctx) { @@ -2240,18 +1987,31 @@ static void sl_send_selection_data(struct sl_context* ctx) { static const uint32_t sl_incr_chunk_size = 64 * 1024; static int sl_handle_selection_fd_readable(int fd, uint32_t mask, void* data) { - struct sl_context* ctx = data; - int bytes, offset, bytes_left; - void* p; + struct sl_context* ctx = static_cast(data); - offset = ctx->selection_data.size; - if (ctx->selection_data.size < sl_incr_chunk_size) - p = wl_array_add(&ctx->selection_data, sl_incr_chunk_size); - else - p = (char*)ctx->selection_data.data + ctx->selection_data.size; - bytes_left = ctx->selection_data.alloc - offset; + // When a selection starts, the wl_array in |ctx->selection_data| is + // initialized with a size of zero. Since we now need to actually write into + // it, allocate |sl_incr_chunk_size| bytes to store the selection data in. We + // need to buffer this much to decide between a one-shot transfer and an + // incremental transfer, as this decision must be made before the first + // response is sent. + if (ctx->selection_data.alloc == 0) { + // wl_array_add is ostensibly failable, but the only failure case comes from + // calling malloc, and if that fails we should just die anyway. + errno_assert( + (size_t)wl_array_add(&ctx->selection_data, sl_incr_chunk_size)); - bytes = read(fd, p, bytes_left); + // wl_array_add increments |size| as well as |alloc|, but we don't actually + // want that yet. Instead we will set |size| later based on the results of + // the read call. + ctx->selection_data.size -= sl_incr_chunk_size; + } + + int offset = ctx->selection_data.size; + void* p = reinterpret_cast(ctx->selection_data.data) + offset; + int bytes_left = ctx->selection_data.alloc - offset; + + int bytes = read(fd, p, bytes_left); if (bytes == -1) { fprintf(stderr, "read error from data source: %m\n"); sl_send_selection_notify(ctx, XCB_ATOM_NONE); @@ -2288,33 +2048,58 @@ static int sl_handle_selection_fd_readable(int fd, uint32_t mask, void* data) { } } - wl_event_source_remove(ctx->selection_event_source); - ctx->selection_event_source = NULL; + ctx->selection_event_source.reset(); return 1; } -static void sl_handle_property_notify(struct sl_context* ctx, - xcb_property_notify_event_t* event) { - if (event->atom == XCB_ATOM_WM_NAME) { +void sl_handle_property_notify(struct sl_context* ctx, + xcb_property_notify_event_t* event) { + TRACE_EVENT("x11wm", "XCB_PROPERTY_NOTIFY", [&](perfetto::EventContext p) { + perfetto_annotate_atom(ctx, p, "event->atom", event->atom); + perfetto_annotate_xcb_property_state(p, "event->state", event->state); + perfetto_annotate_window(ctx, p, "event->window", event->window); + }); + if (event->atom == XCB_ATOM_WM_NAME || + event->atom == ctx->atoms[ATOM_NET_WM_NAME].value) { struct sl_window* window = sl_lookup_window(ctx, event->window); if (!window) return; + bool atom_is_net_wm_name = + event->atom == ctx->atoms[ATOM_NET_WM_NAME].value; + + // If _NET_WM_NAME is set, ignore changes to WM_NAME. + if (!atom_is_net_wm_name && window->has_net_wm_name) + return; + if (window->name) { free(window->name); window->name = NULL; + window->has_net_wm_name = false; } - if (event->state != XCB_PROPERTY_DELETE) { + xcb_atom_t atom = XCB_ATOM_NONE; + if (event->state != XCB_PROPERTY_DELETE) + atom = event->atom; + else if (atom_is_net_wm_name) + // If _NET_WM_NAME is deleted, fall back to WM_NAME if it is set. + atom = XCB_ATOM_WM_NAME; + + if (atom != XCB_ATOM_NONE) { xcb_get_property_reply_t* reply = xcb_get_property_reply( ctx->connection, - xcb_get_property(ctx->connection, 0, window->id, XCB_ATOM_WM_NAME, - XCB_ATOM_ANY, 0, 2048), + xcb_get_property(ctx->connection, 0, window->id, atom, XCB_ATOM_ANY, + 0, 2048), NULL); if (reply) { - window->name = strndup(xcb_get_property_value(reply), - xcb_get_property_value_length(reply)); + window->name = + strndup(static_cast(xcb_get_property_value(reply)), + xcb_get_property_value_length(reply)); free(reply); + + if (atom == ctx->atoms[ATOM_NET_WM_NAME].value) { + window->has_net_wm_name = true; + } } } @@ -2322,9 +2107,42 @@ static void sl_handle_property_notify(struct sl_context* ctx, return; if (window->name) { - zxdg_toplevel_v6_set_title(window->xdg_toplevel, window->name); + xdg_toplevel_set_title(window->xdg_toplevel, window->name); } else { - zxdg_toplevel_v6_set_title(window->xdg_toplevel, ""); + xdg_toplevel_set_title(window->xdg_toplevel, ""); + } + } else if (event->atom == XCB_ATOM_WM_CLASS) { + struct sl_window* window = sl_lookup_window(ctx, event->window); + if (!window || event->state == XCB_PROPERTY_DELETE) + return; + + xcb_get_property_cookie_t cookie = + xcb_get_property(ctx->connection, 0, window->id, XCB_ATOM_WM_CLASS, + XCB_ATOM_ANY, 0, 2048); + xcb_get_property_reply_t* reply = + xcb_get_property_reply(ctx->connection, cookie, NULL); + if (reply) { + sl_decode_wm_class(window, reply); + free(reply); + } + sl_update_application_id(ctx, window); + } else if (event->atom == ctx->application_id_property_atom) { + struct sl_window* window = sl_lookup_window(ctx, event->window); + if (!window || event->state == XCB_PROPERTY_DELETE) + return; + + // TODO(cpelling): Support other atom types (e.g. strings) if/when a use + // case arises. The current use case is for cardinals (uint32) but this + // is easy enough to extend later. + xcb_get_property_cookie_t cookie = xcb_get_property( + ctx->connection, 0, window->id, ctx->application_id_property_atom, + XCB_ATOM_CARDINAL, 0, 1); + xcb_get_property_reply_t* reply = + xcb_get_property_reply(ctx->connection, cookie, NULL); + if (reply) { + sl_set_application_id_from_atom(ctx, window, reply); + sl_update_application_id(ctx, window); + free(reply); } } else if (event->atom == XCB_ATOM_WM_NORMAL_HINTS) { struct sl_window* window = sl_lookup_window(ctx, event->window); @@ -2345,6 +2163,10 @@ static void sl_handle_property_notify(struct sl_context* ctx, memcpy(&size_hints, xcb_get_property_value(reply), sizeof(size_hints)); free(reply); } + TRACE_EVENT("x11wm", "XCB_PROPERTY_NOTIFY: XCB_ATOM_WM_NORMAL_HINTS", + [&](perfetto::EventContext p) { + perfetto_annotate_size_hints(p, size_hints); + }); window->size_flags |= size_hints.flags & (P_MIN_SIZE | P_MAX_SIZE); if (window->size_flags & P_MIN_SIZE) { @@ -2361,19 +2183,47 @@ static void sl_handle_property_notify(struct sl_context* ctx, return; if (window->size_flags & P_MIN_SIZE) { - zxdg_toplevel_v6_set_min_size(window->xdg_toplevel, - window->min_width / ctx->scale, - window->min_height / ctx->scale); + int32_t minw = window->min_width; + int32_t minh = window->min_height; + + sl_transform_guest_to_host(window->ctx, window->paired_surface, &minw, + &minh); + xdg_toplevel_set_min_size(window->xdg_toplevel, minw, minh); } else { - zxdg_toplevel_v6_set_min_size(window->xdg_toplevel, 0, 0); + xdg_toplevel_set_min_size(window->xdg_toplevel, 0, 0); } if (window->size_flags & P_MAX_SIZE) { - zxdg_toplevel_v6_set_max_size(window->xdg_toplevel, - window->max_width / ctx->scale, - window->max_height / ctx->scale); + int32_t maxw = window->max_width; + int32_t maxh = window->max_height; + + sl_transform_guest_to_host(window->ctx, window->paired_surface, &maxw, + &maxh); + xdg_toplevel_set_max_size(window->xdg_toplevel, maxw, maxh); } else { - zxdg_toplevel_v6_set_max_size(window->xdg_toplevel, 0, 0); + xdg_toplevel_set_max_size(window->xdg_toplevel, 0, 0); + } + } else if (event->atom == XCB_ATOM_WM_HINTS) { + struct sl_window* window = sl_lookup_window(ctx, event->window); + if (!window) + return; + + if (event->state == XCB_PROPERTY_DELETE) + return; + struct sl_wm_hints wm_hints = {0}; + xcb_get_property_reply_t* reply = xcb_get_property_reply( + ctx->connection, + xcb_get_property(ctx->connection, 0, window->id, XCB_ATOM_WM_HINTS, + XCB_ATOM_ANY, 0, sizeof(wm_hints)), + NULL); + + if (!reply) + return; + memcpy(&wm_hints, xcb_get_property_value(reply), sizeof(wm_hints)); + free(reply); + + if (wm_hints.flags & WM_HINTS_FLAG_URGENCY) { + sl_request_attention(ctx, window, /*is_strong_request=*/false); } } else if (event->atom == ctx->atoms[ATOM_MOTIF_WM_HINTS].value) { struct sl_window* window = sl_lookup_window(ctx, event->window); @@ -2392,6 +2242,11 @@ static void sl_handle_property_notify(struct sl_context* ctx, 0, sizeof(mwm_hints)), NULL); if (reply) { + if (xcb_get_property_value_length(reply) >= + static_cast(sizeof(mwm_hints))) { + memcpy(&mwm_hints, xcb_get_property_value(reply), sizeof(mwm_hints)); + } + free(reply); if (mwm_hints.flags & MWM_HINTS_DECORATIONS) { if (mwm_hints.decorations & MWM_DECOR_ALL) window->decorated = ~mwm_hints.decorations & MWM_DECOR_TITLE; @@ -2405,11 +2260,10 @@ static void sl_handle_property_notify(struct sl_context* ctx, return; zaura_surface_set_frame(window->aura_surface, - window->decorated - ? ZAURA_SURFACE_FRAME_TYPE_NORMAL - : window->depth == 32 - ? ZAURA_SURFACE_FRAME_TYPE_NONE - : ZAURA_SURFACE_FRAME_TYPE_SHADOW); + window->decorated ? ZAURA_SURFACE_FRAME_TYPE_NORMAL + : window->depth == 32 + ? ZAURA_SURFACE_FRAME_TYPE_NONE + : ZAURA_SURFACE_FRAME_TYPE_SHADOW); } else if (event->atom == ctx->atoms[ATOM_GTK_THEME_VARIANT].value) { struct sl_window* window; uint32_t frame_color; @@ -2429,7 +2283,8 @@ static void sl_handle_property_notify(struct sl_context* ctx, NULL); if (reply) { if (xcb_get_property_value_length(reply) >= 4) - window->dark_frame = !strcmp(xcb_get_property_value(reply), "dark"); + window->dark_frame = !strcmp( + static_cast(xcb_get_property_value(reply)), "dark"); free(reply); } } @@ -2440,6 +2295,23 @@ static void sl_handle_property_notify(struct sl_context* ctx, frame_color = window->dark_frame ? ctx->dark_frame_color : ctx->frame_color; zaura_surface_set_frame_colors(window->aura_surface, frame_color, frame_color); + } else if (event->atom == + ctx->atoms[ATOM_XWAYLAND_RANDR_EMU_MONITOR_RECTS].value) { + TRACE_EVENT("x11wm", + "XCB_PROPERTY_NOTIFY: _XWAYLAND_RANDR_EMU_MONITOR_RECTS", + [&](perfetto::EventContext p) { + xcb_get_property_cookie_t cookie = xcb_get_property( + ctx->connection, 0, event->window, + ctx->atoms[ATOM_XWAYLAND_RANDR_EMU_MONITOR_RECTS].value, + XCB_ATOM_ANY, 0, 2048); + + perfetto_annotate_window(ctx, p, "window", event->window); + + xcb_get_property_reply_t* reply = + xcb_get_property_reply(ctx->connection, cookie, NULL); + perfetto_annotate_cardinal_list(p, "value", reply); + free(reply); + }); } else if (event->atom == ctx->atoms[ATOM_WL_SELECTION].value) { if (event->window == ctx->selection_window && event->state == XCB_PROPERTY_NEW_VALUE && @@ -2480,10 +2352,10 @@ static void sl_handle_property_notify(struct sl_context* ctx, sl_send_selection_data(ctx); if (!ctx->selection_event_source) { - ctx->selection_event_source = wl_event_loop_add_fd( + ctx->selection_event_source.reset(wl_event_loop_add_fd( wl_display_get_event_loop(ctx->host_display), ctx->selection_data_offer_receive_fd, WL_EVENT_READABLE, - sl_handle_selection_fd_readable, ctx); + sl_handle_selection_fd_readable, ctx)); } return; } @@ -2507,7 +2379,8 @@ static void sl_internal_data_source_send(void* data, struct wl_data_source* data_source, const char* mime_type, int32_t fd) { - struct sl_data_source* host = data; + TRACE_EVENT("other", "sl_internal_data_source_send"); + struct sl_data_source* host = static_cast(data); struct sl_context* ctx = host->ctx; xcb_intern_atom_cookie_t cookie = @@ -2517,7 +2390,8 @@ static void sl_internal_data_source_send(void* data, sl_begin_data_source_send(ctx, fd, cookie, host); } else { struct sl_data_source_send_request* request = - malloc(sizeof(struct sl_data_source_send_request)); + static_cast( + malloc(sizeof(struct sl_data_source_send_request))); request->fd = fd; request->cookie = cookie; @@ -2528,7 +2402,8 @@ static void sl_internal_data_source_send(void* data, static void sl_internal_data_source_cancelled( void* data, struct wl_data_source* data_source) { - struct sl_data_source* host = data; + TRACE_EVENT("other", "sl_internal_data_source_cancelled"); + struct sl_data_source* host = static_cast(data); if (host->ctx->selection_data_source == host) host->ctx->selection_data_source = NULL; @@ -2546,13 +2421,14 @@ char* sl_copy_atom_name(xcb_get_atom_name_reply_t* reply) { // ourselves. char* name_start = xcb_get_atom_name_name(reply); int name_len = xcb_get_atom_name_name_length(reply); - char* name = malloc(name_len + 1); + char* name = static_cast(malloc(name_len + 1)); memcpy(name, name_start, name_len); name[name_len] = '\0'; return name; } static void sl_get_selection_targets(struct sl_context* ctx) { + TRACE_EVENT("other", "sl_get_selection_targets"); struct sl_data_source* data_source = NULL; xcb_get_property_reply_t* reply; xcb_atom_t* value; @@ -2562,7 +2438,7 @@ static void sl_get_selection_targets(struct sl_context* ctx) { ctx->connection, xcb_get_property(ctx->connection, 1, ctx->selection_window, ctx->atoms[ATOM_WL_SELECTION].value, - XCB_GET_PROPERTY_TYPE_ANY, 0, 4096), + XCB_GET_PROPERTY_TYPE_ANY, 0, DEFAULT_BUFFER_SIZE), NULL); if (!reply) return; @@ -2573,7 +2449,7 @@ static void sl_get_selection_targets(struct sl_context* ctx) { } if (ctx->data_device_manager) { - data_source = malloc(sizeof(*data_source)); + data_source = static_cast(malloc(sizeof(*data_source))); assert(data_source); data_source->ctx = ctx; @@ -2582,7 +2458,7 @@ static void sl_get_selection_targets(struct sl_context* ctx) { wl_data_source_add_listener(data_source->internal, &sl_internal_data_source_listener, data_source); - value = xcb_get_property_value(reply); + value = static_cast(xcb_get_property_value(reply)); // We need to convert all of the offered target types from X11 atoms to // strings (i.e. getting the names of the atoms). Each conversion requires a @@ -2591,7 +2467,8 @@ static void sl_get_selection_targets(struct sl_context* ctx) { // as a batch with xcb_get_atom_name, and then read all the replies as a // batch with xcb_get_atom_name_reply. xcb_get_atom_name_cookie_t* atom_name_cookies = - malloc(sizeof(xcb_get_atom_name_cookie_t) * reply->value_len); + static_cast( + malloc(sizeof(xcb_get_atom_name_cookie_t) * reply->value_len)); for (i = 0; i < reply->value_len; i++) { atom_name_cookies[i] = xcb_get_atom_name(ctx->connection, value[i]); } @@ -2624,6 +2501,7 @@ static void sl_get_selection_targets(struct sl_context* ctx) { } static void sl_get_selection_data(struct sl_context* ctx) { + TRACE_EVENT("other", "sl_get_selection_data"); xcb_get_property_reply_t* reply = xcb_get_property_reply( ctx->connection, xcb_get_property(ctx->connection, 1, ctx->selection_window, @@ -2673,6 +2551,7 @@ static void sl_send_timestamp(struct sl_context* ctx) { } static void sl_send_data(struct sl_context* ctx, xcb_atom_t data_type) { + TRACE_EVENT("other", "sl_send_data"); int rv, fd_to_receive, fd_to_wayland; if (!ctx->selection_data_offer) { @@ -2696,37 +2575,27 @@ static void sl_send_data(struct sl_context* ctx, xcb_atom_t data_type) { wl_array_init(&ctx->selection_data); ctx->selection_data_ack_pending = 0; - switch (ctx->data_driver) { - case DATA_DRIVER_VIRTWL: { - struct virtwl_ioctl_new new_pipe = { - .type = VIRTWL_IOCTL_NEW_PIPE_READ, - .fd = -1, - .flags = 0, - .size = 0, - }; + if (ctx->channel == NULL) { + // Running in noop mode, without virtualization. + int p[2]; - rv = ioctl(ctx->virtwl_fd, VIRTWL_IOCTL_NEW, &new_pipe); - if (rv) { - fprintf(stderr, "error: failed to create virtwl pipe: %s\n", - strerror(errno)); - sl_send_selection_notify(ctx, XCB_ATOM_NONE); - return; - } + rv = pipe2(p, O_CLOEXEC | O_NONBLOCK); + errno_assert(!rv); - fd_to_receive = new_pipe.fd; - fd_to_wayland = new_pipe.fd; + fd_to_receive = p[0]; + fd_to_wayland = p[1]; + } else { + int pipe_fd; + rv = ctx->channel->create_pipe(pipe_fd); + if (rv) { + fprintf(stderr, "error: failed to create virtwl pipe: %s\n", + strerror(-rv)); + sl_send_selection_notify(ctx, XCB_ATOM_NONE); + return; + } - } break; - case DATA_DRIVER_NOOP: { - int p[2]; - - rv = pipe2(p, O_CLOEXEC | O_NONBLOCK); - assert(!rv); - - fd_to_receive = p[0]; - fd_to_wayland = p[1]; - - } break; + fd_to_receive = pipe_fd; + fd_to_wayland = pipe_fd; } xcb_get_atom_name_reply_t* atom_name_reply = @@ -2741,10 +2610,10 @@ static void sl_send_data(struct sl_context* ctx, xcb_atom_t data_type) { free(atom_name_reply); free(name); - ctx->selection_event_source = wl_event_loop_add_fd( + ctx->selection_event_source.reset(wl_event_loop_add_fd( wl_display_get_event_loop(ctx->host_display), ctx->selection_data_offer_receive_fd, WL_EVENT_READABLE, - sl_handle_selection_fd_readable, ctx); + sl_handle_selection_fd_readable, ctx)); } else { // If getting the atom name failed, notify the requestor that there won't be // any data, and close our end of the pipe. @@ -2776,7 +2645,7 @@ static void sl_handle_selection_request(struct sl_context* ctx, } else { int success = 0; xcb_atom_t* atom; - wl_array_for_each(atom, &ctx->selection_data_offer->atoms) { + sl_array_for_each(atom, &ctx->selection_data_offer->atoms) { if (event->target == *atom) { success = 1; sl_send_data(ctx, *atom); @@ -2822,66 +2691,98 @@ static void sl_handle_xfixes_selection_notify( } static int sl_handle_x_connection_event(int fd, uint32_t mask, void* data) { + TRACE_EVENT("other", "sl_handle_x_connection_event"); struct sl_context* ctx = (struct sl_context*)data; xcb_generic_event_t* event; uint32_t count = 0; - if ((mask & WL_EVENT_HANGUP) || (mask & WL_EVENT_ERROR)) - return 0; + if ((mask & WL_EVENT_HANGUP) || (mask & WL_EVENT_ERROR)) { + fprintf(stderr, "Got error or hangup (mask %d) on X connection, exiting\n", + mask); + exit(EXIT_SUCCESS); + } while ((event = xcb_poll_for_event(ctx->connection))) { switch (event->response_type & ~SEND_EVENT_MASK) { case XCB_CREATE_NOTIFY: - sl_handle_create_notify(ctx, (xcb_create_notify_event_t*)event); + sl_handle_create_notify( + ctx, reinterpret_cast(event)); break; case XCB_DESTROY_NOTIFY: - sl_handle_destroy_notify(ctx, (xcb_destroy_notify_event_t*)event); + sl_handle_destroy_notify( + ctx, reinterpret_cast(event)); break; case XCB_REPARENT_NOTIFY: - sl_handle_reparent_notify(ctx, (xcb_reparent_notify_event_t*)event); + sl_handle_reparent_notify( + ctx, reinterpret_cast(event)); break; case XCB_MAP_REQUEST: - sl_handle_map_request(ctx, (xcb_map_request_event_t*)event); + sl_handle_map_request( + ctx, reinterpret_cast(event)); break; case XCB_MAP_NOTIFY: - sl_handle_map_notify(ctx, (xcb_map_notify_event_t*)event); + sl_handle_map_notify(ctx, + reinterpret_cast(event)); break; case XCB_UNMAP_NOTIFY: - sl_handle_unmap_notify(ctx, (xcb_unmap_notify_event_t*)event); + sl_handle_unmap_notify( + ctx, reinterpret_cast(event)); break; case XCB_CONFIGURE_REQUEST: - sl_handle_configure_request(ctx, (xcb_configure_request_event_t*)event); + sl_handle_configure_request( + ctx, reinterpret_cast(event)); break; case XCB_CONFIGURE_NOTIFY: - sl_handle_configure_notify(ctx, (xcb_configure_notify_event_t*)event); + sl_handle_configure_notify( + ctx, reinterpret_cast(event)); break; case XCB_CLIENT_MESSAGE: - sl_handle_client_message(ctx, (xcb_client_message_event_t*)event); + sl_handle_client_message( + ctx, reinterpret_cast(event)); break; case XCB_FOCUS_IN: - sl_handle_focus_in(ctx, (xcb_focus_in_event_t*)event); + sl_handle_focus_in(ctx, reinterpret_cast(event)); break; case XCB_FOCUS_OUT: - sl_handle_focus_out(ctx, (xcb_focus_out_event_t*)event); + sl_handle_focus_out(ctx, + reinterpret_cast(event)); break; case XCB_PROPERTY_NOTIFY: - sl_handle_property_notify(ctx, (xcb_property_notify_event_t*)event); + sl_handle_property_notify( + ctx, reinterpret_cast(event)); break; case XCB_SELECTION_NOTIFY: - sl_handle_selection_notify(ctx, (xcb_selection_notify_event_t*)event); + sl_handle_selection_notify( + ctx, reinterpret_cast(event)); break; case XCB_SELECTION_REQUEST: - sl_handle_selection_request(ctx, (xcb_selection_request_event_t*)event); + sl_handle_selection_request( + ctx, reinterpret_cast(event)); break; } switch (event->response_type - ctx->xfixes_extension->first_event) { case XCB_XFIXES_SELECTION_NOTIFY: sl_handle_xfixes_selection_notify( - ctx, (xcb_xfixes_selection_notify_event_t*)event); + ctx, reinterpret_cast(event)); break; } + // Xshape specific events extend the normal event numbers + // The first event id is retrieved when querying for xshape + // extension information. This can be used to determine + // if the event that is received is Xshape specific. + if (ctx->enable_xshape) { + uint8_t xshape_event_id = + event->response_type - ctx->xshape_extension->first_event; + switch (xshape_event_id) { + case XCB_SHAPE_NOTIFY: + sl_handle_shape_notify( + ctx, reinterpret_cast(event)); + break; + } + } + free(event); ++count; } @@ -2892,7 +2793,73 @@ static int sl_handle_x_connection_event(int fd, uint32_t mask, void* data) { return count; } +static void sl_set_supported(struct sl_context* ctx) { + const xcb_atom_t supported_atoms[] = { + ctx->atoms[ATOM_NET_ACTIVE_WINDOW].value, + ctx->atoms[ATOM_NET_WM_MOVERESIZE].value, + ctx->atoms[ATOM_NET_WM_NAME].value, + ctx->atoms[ATOM_NET_WM_STATE].value, + ctx->atoms[ATOM_NET_WM_STATE_FULLSCREEN].value, + ctx->atoms[ATOM_NET_WM_STATE_MAXIMIZED_VERT].value, + ctx->atoms[ATOM_NET_WM_STATE_MAXIMIZED_HORZ].value, + ctx->atoms[ATOM_NET_WM_STATE_FOCUSED].value, + // TODO(hollingum): STATE_MODAL and CLIENT_LIST, based on what wlroots + // has. + }; + + xcb_change_property(ctx->connection, XCB_PROP_MODE_REPLACE, ctx->screen->root, + ctx->atoms[ATOM_NET_SUPPORTED].value, XCB_ATOM_ATOM, 32, + sizeof(supported_atoms) / sizeof(xcb_atom_t), + supported_atoms); +} + +// The window manager is responsible for setting the default cursor on the root +// window, which will be used by applications that do not try to set their own +// cursor. If we do not do this, the cursor will be invisible, see b/204724669 +// for details. +static void sl_initialize_cursor(struct sl_context* ctx) { + xcb_generic_error_t* err; + xcb_void_cookie_t cookie; + + // Load the "cursor" font, which is a built-in set of fonts, documented at + // https://tronche.com/gui/x/xlib/appendix/b/. + xcb_font_t font_id = xcb_generate_id(ctx->connection); + cookie = xcb_open_font_checked(ctx->connection, font_id, strlen("cursor"), + "cursor"); + err = xcb_request_check(ctx->connection, cookie); + assert(!err); + + // Create the standard "left ptr" cursor. The below call is a bit technical, + // but it means: + // - font_id appears twice because we use a mask (background) whose font is + // the same as the source. + // - the mask is the source + 1, because the inbuilt cursor font has + // even-numbered foreground glyphs and odd-numbered backgrounds. + // - The numbers are 16-bit RGB for the foreground and background, so this + // has a black foreground and a white background. + constexpr int left_ptr_cursor_id = 68; + xcb_cursor_t cursor_id = xcb_generate_id(ctx->connection); + cookie = xcb_create_glyph_cursor_checked( + ctx->connection, cursor_id, font_id, font_id, left_ptr_cursor_id, + left_ptr_cursor_id + 1, 0, 0, 0, 65535, 65535, 65535); + err = xcb_request_check(ctx->connection, cookie); + assert(!err); + + // Set the cursor for the root window. + cookie = xcb_change_window_attributes_checked( + ctx->connection, ctx->screen->root, XCB_CW_CURSOR, &cursor_id); + err = xcb_request_check(ctx->connection, cookie); + assert(!err); + + // Free the cursor and the fonts, since we no-longer need them. + xcb_free_cursor(ctx->connection, cursor_id); + cookie = xcb_close_font_checked(ctx->connection, font_id); + err = xcb_request_check(ctx->connection, cookie); + assert(!err); +} + static void sl_connect(struct sl_context* ctx) { + TRACE_EVENT("other", "sl_connect"); const char wm_name[] = "Sommelier"; const xcb_setup_t* setup; xcb_screen_iterator_t screen_iterator; @@ -2902,6 +2869,7 @@ static void sl_connect(struct sl_context* ctx) { xcb_intern_atom_reply_t* atom_reply; xcb_depth_iterator_t depth_iterator; xcb_xfixes_query_version_reply_t* xfixes_query_version_reply; + xcb_shape_query_version_reply_t* xshape_query_version_reply; const xcb_query_extension_reply_t* composite_extension; unsigned i; @@ -2911,11 +2879,18 @@ static void sl_connect(struct sl_context* ctx) { xcb_prefetch_extension_data(ctx->connection, &xcb_xfixes_id); xcb_prefetch_extension_data(ctx->connection, &xcb_composite_id); + // Send requests to fetch/create ("intern") all the atoms we'll need later. for (i = 0; i < ARRAY_SIZE(ctx->atoms); ++i) { const char* name = ctx->atoms[i].name; ctx->atoms[i].cookie = xcb_intern_atom(ctx->connection, 0, strlen(name), name); } + xcb_intern_atom_cookie_t app_id_atom_cookie; + if (ctx->application_id_property_name) { + app_id_atom_cookie = xcb_intern_atom( + ctx->connection, 0, strlen(ctx->application_id_property_name), + ctx->application_id_property_name); + } setup = xcb_get_setup(ctx->connection); screen_iterator = xcb_setup_roots_iterator(setup); @@ -2928,10 +2903,10 @@ static void sl_connect(struct sl_context* ctx) { change_attributes_cookie = xcb_change_window_attributes( ctx->connection, ctx->screen->root, XCB_CW_EVENT_MASK, values); - ctx->connection_event_source = wl_event_loop_add_fd( + ctx->connection_event_source.reset(wl_event_loop_add_fd( wl_display_get_event_loop(ctx->host_display), xcb_get_file_descriptor(ctx->connection), WL_EVENT_READABLE, - &sl_handle_x_connection_event, ctx); + &sl_handle_x_connection_event, ctx)); ctx->xfixes_extension = xcb_get_extension_data(ctx->connection, &xcb_xfixes_id); @@ -2951,6 +2926,24 @@ static void sl_connect(struct sl_context* ctx) { assert(composite_extension->present); UNUSED(composite_extension); + if (ctx->enable_xshape) { + xcb_prefetch_extension_data(ctx->connection, &xcb_shape_id); + + ctx->xshape_extension = + xcb_get_extension_data(ctx->connection, &xcb_shape_id); + + xshape_query_version_reply = xcb_shape_query_version_reply( + ctx->connection, xcb_shape_query_version(ctx->connection), NULL); + + assert(xshape_query_version_reply); + assert(xshape_query_version_reply->major_version >= + XCB_SHAPE_MAJOR_VERSION); + assert(xshape_query_version_reply->minor_version >= + XCB_SHAPE_MINOR_VERSION); + free(xshape_query_version_reply); + xshape_query_version_reply = NULL; + } + redirect_subwindows_cookie = xcb_composite_redirect_subwindows_checked( ctx->connection, ctx->screen->root, XCB_COMPOSITE_REDIRECT_MANUAL); @@ -2967,6 +2960,7 @@ static void sl_connect(struct sl_context* ctx) { 1, 0, XCB_WINDOW_CLASS_INPUT_ONLY, XCB_COPY_FROM_PARENT, 0, NULL); + // Wait on results for all the atom intern requests we sent above. for (i = 0; i < ARRAY_SIZE(ctx->atoms); ++i) { atom_reply = xcb_intern_atom_reply(ctx->connection, ctx->atoms[i].cookie, &error); @@ -2974,6 +2968,13 @@ static void sl_connect(struct sl_context* ctx) { ctx->atoms[i].value = atom_reply->atom; free(atom_reply); } + if (ctx->application_id_property_name) { + atom_reply = + xcb_intern_atom_reply(ctx->connection, app_id_atom_cookie, &error); + assert(!error); + ctx->application_id_property_atom = atom_reply->atom; + free(atom_reply); + } depth_iterator = xcb_screen_allowed_depths_iterator(ctx->screen); while (depth_iterator.rem > 0) { @@ -3024,12 +3025,15 @@ static void sl_connect(struct sl_context* ctx) { xcb_change_property(ctx->connection, XCB_PROP_MODE_REPLACE, ctx->screen->root, ctx->atoms[ATOM_NET_SUPPORTING_WM_CHECK].value, XCB_ATOM_WINDOW, 32, 1, &ctx->window); + sl_set_supported(ctx); xcb_set_selection_owner(ctx->connection, ctx->window, ctx->atoms[ATOM_WM_S0].value, XCB_CURRENT_TIME); xcb_set_input_focus(ctx->connection, XCB_INPUT_FOCUS_NONE, XCB_NONE, XCB_CURRENT_TIME); xcb_flush(ctx->connection); + + sl_initialize_cursor(ctx); } static void sl_sd_notify(const char* state) { @@ -3044,14 +3048,15 @@ static void sl_sd_notify(const char* state) { assert(socket_name); fd = socket(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0); - assert(fd >= 0); + errno_assert(fd >= 0); memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_UNIX; strncpy(addr.sun_path, socket_name, sizeof(addr.sun_path)); memset(&iovec, 0, sizeof(iovec)); - iovec.iov_base = (char*)state; + // iovec is just going to be used to send data as part of a const msghdr. + iovec.iov_base = const_cast(state); iovec.iov_len = strlen(state); memset(&msghdr, 0, sizeof(msghdr)); @@ -3062,8 +3067,7 @@ static void sl_sd_notify(const char* state) { msghdr.msg_iovlen = 1; rv = sendmsg(fd, &msghdr, MSG_NOSIGNAL); - assert(rv != -1); - UNUSED(rv); + errno_assert(rv != -1); } static int sl_handle_sigchld(int signal_number, void* data) { @@ -3099,6 +3103,16 @@ static int sl_handle_sigchld(int signal_number, void* data) { return 1; } +static int sl_handle_sigusr1(int signal_number, void* data) { + struct sl_context* ctx = (struct sl_context*)data; + fprintf(stderr, "dumping trace %s\n", ctx->trace_filename); + dump_trace(ctx->trace_filename); + if (ctx->timing != NULL) { + ctx->timing->OutputLog(); + } + return 1; +} + static void sl_execvp(const char* file, char* const argv[], int wayland_socked_fd) { @@ -3117,33 +3131,34 @@ static void sl_execvp(const char* file, static void sl_calculate_scale_for_xwayland(struct sl_context* ctx) { struct sl_host_output* output; double default_scale_factor = 1.0; - double scale; + double scale = ctx->desired_scale; - // Find internal output and determine preferred scale factor. - wl_list_for_each(output, &ctx->host_outputs, link) { - if (output->internal) { - double preferred_scale = - sl_output_aura_scale_factor_to_double(output->preferred_scale); + if (!ctx->use_direct_scale) { + // Find internal output and determine preferred scale factor. + wl_list_for_each(output, &ctx->host_outputs, link) { + if (output->internal) { + double preferred_scale = + sl_output_aura_scale_factor_to_double(output->preferred_scale); - if (ctx->aura_shell) { - double device_scale_factor = - sl_output_aura_scale_factor_to_double(output->device_scale_factor); + if (ctx->aura_shell) { + double device_scale_factor = sl_output_aura_scale_factor_to_double( + output->device_scale_factor); - default_scale_factor = device_scale_factor * preferred_scale; + default_scale_factor = device_scale_factor * preferred_scale; + } + break; } - break; } + // We use the default scale factor multiplied by desired scale set by the + // user. This gives us HiDPI support by default but the user can still + // adjust it if higher or lower density is preferred. + scale = ctx->desired_scale * default_scale_factor; + + // Round to integer scale if wp_viewporter interface is not present. + if (!ctx->viewporter) + scale = round(scale); } - // We use the default scale factor multipled by desired scale set by the - // user. This gives us HiDPI support by default but the user can still - // adjust it if higher or lower density is preferred. - scale = ctx->desired_scale * default_scale_factor; - - // Round to integer scale if wp_viewporter interface is not present. - if (!ctx->viewporter) - scale = round(scale); - // Clamp and set scale. ctx->scale = MIN(MAX_SCALE, MAX(MIN_SCALE, scale)); @@ -3152,14 +3167,30 @@ static void sl_calculate_scale_for_xwayland(struct sl_context* ctx) { sl_output_send_host_output_state(output); } +// Extra connections are made to XWayland-hosting instances for IME support. +static void sl_extra_client_created_notify(struct wl_listener* listener, + void* data) { + sl_context* ctx = + wl_container_of(listener, ctx, extra_client_created_listener); + wl_client* client = static_cast(data); + + sl_set_display_implementation(ctx, client); +} + static int sl_handle_display_ready_event(int fd, uint32_t mask, void* data) { + TRACE_EVENT("surface", "sl_handle_display_ready_event"); struct sl_context* ctx = (struct sl_context*)data; char display_name[9]; int bytes_read = 0; pid_t pid; - if (!(mask & WL_EVENT_READABLE)) - return 0; + if (!(mask & WL_EVENT_READABLE)) { + fprintf(stderr, + "Got error or hangup on display ready connection" + " (mask %d), exiting\n", + mask); + exit(EXIT_SUCCESS); + } display_name[0] = ':'; do { @@ -3181,8 +3212,7 @@ static int sl_handle_display_ready_event(int fd, uint32_t mask, void* data) { sl_connect(ctx); - wl_event_source_remove(ctx->display_ready_event_source); - ctx->display_ready_event_source = NULL; + ctx->display_ready_event_source.reset(); close(fd); // Calculate scale now that the default scale factor is known. This also @@ -3191,12 +3221,36 @@ static int sl_handle_display_ready_event(int fd, uint32_t mask, void* data) { sl_calculate_scale_for_xwayland(ctx); wl_display_flush_clients(ctx->host_display); + // Open wayland socket so cros_im can provide IME support to X11 apps. + // This depends on the display number, which is only available once Xwayland + // initialization is complete (now), so we can't create this earlier to use + // it for the wayland connection with Xwayland. + char* socket_name = sl_xasprintf("DISPLAY-%s-wl", display_name); + struct sockaddr_un addr; + int lock_fd; + int sock_fd; + int rv = sl_open_wayland_socket(socket_name, &addr, &lock_fd, &sock_fd); + if (rv >= 0) { + wl_display_add_socket_fd(ctx->host_display, sock_fd); + ctx->extra_client_created_listener.notify = sl_extra_client_created_notify; + wl_display_add_client_created_listener(ctx->host_display, + &ctx->extra_client_created_listener); + } else { + fprintf(stderr, "warning: unable to open wayland socket for cros_im.\n"); + } + free(socket_name); + putenv(sl_xasprintf("XCURSOR_SIZE=%d", - (int)(XCURSOR_SIZE_BASE * ctx->scale + 0.5))); + static_cast(XCURSOR_SIZE_BASE * ctx->scale + 0.5))); pid = fork(); - assert(pid >= 0); + errno_assert(pid >= 0); if (pid == 0) { + // Set WAYLAND_DISPLAY to a value that is guaranteed to not point to a + // valid wayland compositor socket name. Unsetting WAYLAND_DISPLAY is + // insufficient as some clients may attempt to connect to wayland-0 as a + // default fallback. + setenv("WAYLAND_DISPLAY", ".", 1); sl_execvp(ctx->runprog[0], ctx->runprog, -1); _exit(EXIT_FAILURE); } @@ -3215,129 +3269,10 @@ static void sl_client_destroy_notify(struct wl_listener* listener, void* data) { exit(0); } -static int sl_handle_virtwl_ctx_event(int fd, uint32_t mask, void* data) { - struct sl_context* ctx = (struct sl_context*)data; - uint8_t ioctl_buffer[4096]; - struct virtwl_ioctl_txn* ioctl_recv = (struct virtwl_ioctl_txn*)ioctl_buffer; - void* recv_data = ioctl_buffer + sizeof(struct virtwl_ioctl_txn); - size_t max_recv_size = sizeof(ioctl_buffer) - sizeof(struct virtwl_ioctl_txn); - char fd_buffer[CMSG_LEN(sizeof(int) * VIRTWL_SEND_MAX_ALLOCS)]; - struct msghdr msg = {0}; - struct iovec buffer_iov; - ssize_t bytes; - int fd_count; - int rv; - - ioctl_recv->len = max_recv_size; - rv = ioctl(fd, VIRTWL_IOCTL_RECV, ioctl_recv); - if (rv) { - close(ctx->virtwl_socket_fd); - ctx->virtwl_socket_fd = -1; - return 0; - } - - buffer_iov.iov_base = recv_data; - buffer_iov.iov_len = ioctl_recv->len; - - msg.msg_iov = &buffer_iov; - msg.msg_iovlen = 1; - msg.msg_control = fd_buffer; - - // Count how many FDs the kernel gave us. - for (fd_count = 0; fd_count < VIRTWL_SEND_MAX_ALLOCS; fd_count++) { - if (ioctl_recv->fds[fd_count] < 0) - break; - } - if (fd_count) { - struct cmsghdr* cmsg; - - // Need to set msg_controllen so CMSG_FIRSTHDR will return the first - // cmsghdr. We copy every fd we just received from the ioctl into this - // cmsghdr. - msg.msg_controllen = sizeof(fd_buffer); - cmsg = CMSG_FIRSTHDR(&msg); - cmsg->cmsg_level = SOL_SOCKET; - cmsg->cmsg_type = SCM_RIGHTS; - cmsg->cmsg_len = CMSG_LEN(fd_count * sizeof(int)); - memcpy(CMSG_DATA(cmsg), ioctl_recv->fds, fd_count * sizeof(int)); - msg.msg_controllen = cmsg->cmsg_len; - } - - bytes = sendmsg(ctx->virtwl_socket_fd, &msg, MSG_NOSIGNAL); - assert(bytes == ioctl_recv->len); - UNUSED(bytes); - - while (fd_count--) - close(ioctl_recv->fds[fd_count]); - - return 1; -} - -static int sl_handle_virtwl_socket_event(int fd, uint32_t mask, void* data) { - struct sl_context* ctx = (struct sl_context*)data; - uint8_t ioctl_buffer[4096]; - struct virtwl_ioctl_txn* ioctl_send = (struct virtwl_ioctl_txn*)ioctl_buffer; - void* send_data = ioctl_buffer + sizeof(struct virtwl_ioctl_txn); - size_t max_send_size = sizeof(ioctl_buffer) - sizeof(struct virtwl_ioctl_txn); - char fd_buffer[CMSG_LEN(sizeof(int) * VIRTWL_SEND_MAX_ALLOCS)]; - struct iovec buffer_iov; - struct msghdr msg = {0}; - struct cmsghdr* cmsg; - ssize_t bytes; - int fd_count = 0; - int rv; - int i; - - buffer_iov.iov_base = send_data; - buffer_iov.iov_len = max_send_size; - - msg.msg_iov = &buffer_iov; - msg.msg_iovlen = 1; - msg.msg_control = fd_buffer; - msg.msg_controllen = sizeof(fd_buffer); - - bytes = recvmsg(ctx->virtwl_socket_fd, &msg, 0); - assert(bytes > 0); - - // If there were any FDs recv'd by recvmsg, there will be some data in the - // msg_control buffer. To get the FDs out we iterate all cmsghdr's within and - // unpack the FDs if the cmsghdr type is SCM_RIGHTS. - for (cmsg = msg.msg_controllen != 0 ? CMSG_FIRSTHDR(&msg) : NULL; cmsg; - cmsg = CMSG_NXTHDR(&msg, cmsg)) { - size_t cmsg_fd_count; - - if (cmsg->cmsg_level != SOL_SOCKET || cmsg->cmsg_type != SCM_RIGHTS) - continue; - - cmsg_fd_count = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int); - - // fd_count will never exceed VIRTWL_SEND_MAX_ALLOCS because the - // control message buffer only allocates enough space for that many FDs. - memcpy(&ioctl_send->fds[fd_count], CMSG_DATA(cmsg), - cmsg_fd_count * sizeof(int)); - fd_count += cmsg_fd_count; - } - - for (i = fd_count; i < VIRTWL_SEND_MAX_ALLOCS; ++i) - ioctl_send->fds[i] = -1; - - // The FDs and data were extracted from the recvmsg call into the ioctl_send - // structure which we now pass along to the kernel. - ioctl_send->len = bytes; - rv = ioctl(ctx->virtwl_ctx_fd, VIRTWL_IOCTL_SEND, ioctl_send); - assert(!rv); - UNUSED(rv); - - while (fd_count--) - close(ioctl_send->fds[fd_count]); - - return 1; -} - // Break |str| into a sequence of zero or more nonempty arguments. No more // than |argc| arguments will be added to |argv|. Returns the total number of // argments found in |str|. -static int sl_parse_cmd_prefix(char* str, int argc, char** argv) { +static int sl_parse_cmd_prefix(char* str, int argc, char const** argv) { char* s = str; int n = 0; int delim = 0; @@ -3380,26 +3315,45 @@ static void sl_print_usage() { "options:\n" " -h, --help\t\t\tPrint this help\n" " -X\t\t\t\tEnable X11 forwarding\n" - " --master\t\t\tRun as master and spawn child processes\n" + " --parent\t\t\tRun as parent and spawn child processes\n" " --socket=SOCKET\t\tName of socket to listen on\n" " --display=DISPLAY\t\tWayland display to connect to\n" - " --shm-driver=DRIVER\t\tSHM driver to use (noop, dmabuf, virtwl)\n" - " --data-driver=DRIVER\t\tData driver to use (noop, virtwl)\n" " --scale=SCALE\t\t\tScale factor for contents\n" " --dpi=[DPI[,DPI...]]\t\tDPI buckets\n" " --peer-cmd-prefix=PREFIX\tPeer process command line prefix\n" " --accelerators=ACCELERATORS\tList of keyboard accelerators\n" - " --application-id=ID\t\tForced application ID for X11 clients\n" + " --windowed_accelerators=ACCELERATORS\tList of keyboard accelerators\n" + "\tonly while windowed\n" + " --application-id=ID\t\tForced application ID for all X11 windows\n" + " --vm-identifier=NAME\t\tName of the VM, used to identify X11 " + "windows.\n" + "\t\t\t\tIgnored if --application-id is set.\n" + " --application-id-x11-property=PROPERTY\n" + "\tA cardinal window property used to identify X11 windows, as follows:\n" + "\t org.chromium..xprop.\n" + "\tIgnored if --application-id is set.\n" " --x-display=DISPLAY\t\tX11 display to listen on\n" " --xwayland-path=PATH\t\tPath to Xwayland executable\n" " --xwayland-gl-driver-path=PATH\tPath to GL drivers for Xwayland\n" " --xwayland-cmd-prefix=PREFIX\tXwayland command line prefix\n" + " --enable-xshape\t\tEnable X11 XShape extension support\n" " --no-exit-with-child\t\tKeep process alive after child exists\n" " --no-clipboard-manager\tDisable X11 clipboard manager\n" " --frame-color=COLOR\t\tWindow frame color for X11 clients\n" - " --virtwl-device=DEVICE\tVirtWL device to use\n" - " --drm-device=DEVICE\t\tDRM device to use\n" - " --glamor\t\t\tUse glamor to accelerate X11 clients\n"); + " --no-support-damage-buffer\t" + "Disable wl_surface::damage_buffer support.\n" + " --force-drm-device=DEVICE\tDRM device to use\n" + " --glamor\t\t\tUse glamor to accelerate X11 clients\n" + " --timing-filename=PATH\tPath to timing output log\n" + " --direct-scale\t\tEnable direct scaling mode\n" +#ifdef PERFETTO_TRACING + " --trace-filename=PATH\t\tPath to Perfetto trace filename\n" + " --trace-system\t\tPerfetto trace to system daemon\n" +#endif + " --fullscreen-mode=MODE\tDefault fullscreen behavior (immersive," + " plain)\n" + " --noop-driver\t\tPass through to existing Wayland server" + " without virtualization\n"); } static const char* sl_arg_value(const char* arg) { @@ -3411,140 +3365,353 @@ static const char* sl_arg_value(const char* arg) { return s + 1; } -int main(int argc, char** argv) { - struct sl_context ctx = { - .runprog = NULL, - .display = NULL, - .host_display = NULL, - .client = NULL, - .compositor = NULL, - .subcompositor = NULL, - .shm = NULL, - .shell = NULL, - .data_device_manager = NULL, - .xdg_shell = NULL, - .aura_shell = NULL, - .viewporter = NULL, - .linux_dmabuf = NULL, - .keyboard_extension = NULL, - .text_input_manager = NULL, - .display_event_source = NULL, - .display_ready_event_source = NULL, - .sigchld_event_source = NULL, - .shm_driver = SHM_DRIVER_NOOP, - .data_driver = DATA_DRIVER_NOOP, - .wm_fd = -1, - .virtwl_fd = -1, - .virtwl_ctx_fd = -1, - .virtwl_socket_fd = -1, - .virtwl_ctx_event_source = NULL, - .virtwl_socket_event_source = NULL, - .drm_device = NULL, - .gbm = NULL, - .xwayland = 0, - .xwayland_pid = -1, - .child_pid = -1, - .peer_pid = -1, - .xkb_context = NULL, - .next_global_id = 1, - .connection = NULL, - .connection_event_source = NULL, - .xfixes_extension = NULL, - .screen = NULL, - .window = 0, - .host_focus_window = NULL, - .needs_set_input_focus = 0, - .desired_scale = 1.0, - .scale = 1.0, - .application_id = NULL, - .exit_with_child = 1, - .sd_notify = NULL, - .clipboard_manager = 0, - .frame_color = 0xffffffff, - .dark_frame_color = 0xff000000, - .default_seat = NULL, - .selection_window = XCB_WINDOW_NONE, - .selection_owner = XCB_WINDOW_NONE, - .selection_incremental_transfer = 0, - .selection_request = {.requestor = XCB_NONE, .property = XCB_ATOM_NONE}, - .selection_timestamp = XCB_CURRENT_TIME, - .selection_data_device = NULL, - .selection_data_offer = NULL, - .selection_data_source = NULL, - .selection_data_source_send_fd = -1, - .selection_send_event_source = NULL, - .selection_property_reply = NULL, - .selection_property_offset = 0, - .selection_event_source = NULL, - .selection_data_offer_receive_fd = -1, - .selection_data_ack_pending = 0, - .atoms = - { - [ATOM_WM_S0] = {"WM_S0"}, - [ATOM_WM_PROTOCOLS] = {"WM_PROTOCOLS"}, - [ATOM_WM_STATE] = {"WM_STATE"}, - [ATOM_WM_CHANGE_STATE] = {"WM_CHANGE_STATE"}, - [ATOM_WM_DELETE_WINDOW] = {"WM_DELETE_WINDOW"}, - [ATOM_WM_TAKE_FOCUS] = {"WM_TAKE_FOCUS"}, - [ATOM_WM_CLIENT_LEADER] = {"WM_CLIENT_LEADER"}, - [ATOM_WL_SURFACE_ID] = {"WL_SURFACE_ID"}, - [ATOM_UTF8_STRING] = {"UTF8_STRING"}, - [ATOM_MOTIF_WM_HINTS] = {"_MOTIF_WM_HINTS"}, - [ATOM_NET_FRAME_EXTENTS] = {"_NET_FRAME_EXTENTS"}, - [ATOM_NET_STARTUP_ID] = {"_NET_STARTUP_ID"}, - [ATOM_NET_SUPPORTING_WM_CHECK] = {"_NET_SUPPORTING_WM_CHECK"}, - [ATOM_NET_WM_NAME] = {"_NET_WM_NAME"}, - [ATOM_NET_WM_MOVERESIZE] = {"_NET_WM_MOVERESIZE"}, - [ATOM_NET_WM_STATE] = {"_NET_WM_STATE"}, - [ATOM_NET_WM_STATE_FULLSCREEN] = {"_NET_WM_STATE_FULLSCREEN"}, - [ATOM_NET_WM_STATE_MAXIMIZED_VERT] = - {"_NET_WM_STATE_MAXIMIZED_VERT"}, - [ATOM_NET_WM_STATE_MAXIMIZED_HORZ] = - {"_NET_WM_STATE_MAXIMIZED_HORZ"}, - [ATOM_CLIPBOARD] = {"CLIPBOARD"}, - [ATOM_CLIPBOARD_MANAGER] = {"CLIPBOARD_MANAGER"}, - [ATOM_TARGETS] = {"TARGETS"}, - [ATOM_TIMESTAMP] = {"TIMESTAMP"}, - [ATOM_TEXT] = {"TEXT"}, - [ATOM_INCR] = {"INCR"}, - [ATOM_WL_SELECTION] = {"_WL_SELECTION"}, - [ATOM_GTK_THEME_VARIANT] = {"_GTK_THEME_VARIANT"}, - }, - .visual_ids = {0}, - .colormaps = {0}}; +// Parse the list of accelerators that should be reserved by the +// compositor. Format is "|MODIFIERS|KEYSYM", where MODIFIERS is a +// list of modifier names (E.g. ) and KEYSYM is an +// XKB key symbol name (E.g Delete). +// Sommelier will exit with EXIT_FAILURE if this returns false. +// TODO(b/237946069) Confirm accelerator handling works with i18n. +static bool sl_parse_accelerators(struct wl_list* accelerator_list, + const char* accelerators) { + if (accelerators) { + uint32_t modifiers = 0; + + while (*accelerators) { + if (*accelerators == ',') { + accelerators++; + } else if (*accelerators == '<') { + if (strncmp(accelerators, "", 9) == 0) { + modifiers |= CONTROL_MASK; + accelerators += 9; + } else if (strncmp(accelerators, "", 5) == 0) { + modifiers |= ALT_MASK; + accelerators += 5; + } else if (strncmp(accelerators, "", 7) == 0) { + modifiers |= SHIFT_MASK; + accelerators += 7; + } else { + fprintf(stderr, "error: invalid modifier\n"); + return false; + } + } else { + const char* end = strchrnul(accelerators, ','); + char* name = strndup(accelerators, end - accelerators); + + struct sl_accelerator* accelerator = + static_cast(malloc(sizeof(*accelerator))); + accelerator->modifiers = modifiers; + accelerator->symbol = + xkb_keysym_from_name(name, XKB_KEYSYM_CASE_INSENSITIVE); + if (accelerator->symbol == XKB_KEY_NoSymbol) { + fprintf(stderr, "error: invalid key symbol\n"); + return false; + } + + wl_list_insert(accelerator_list, &accelerator->link); + + modifiers = 0; + accelerators = end; + free(name); + } + } + } + return true; +} + +// Takes the lock and then opens the socket for wayland clients to connect to. +// Returns a negative value on error. |addr|, |lock_fd| and |sock_fd| will be +// populated on success. +int sl_open_wayland_socket(const char* socket_name, + struct sockaddr_un* addr, + int* lock_fd, + int* sock_fd) { + int rv; + + const char* runtime_dir = getenv("XDG_RUNTIME_DIR"); + if (!runtime_dir) { + fprintf(stderr, "error: XDG_RUNTIME_DIR not set in the environment\n"); + return -1; + } + + addr->sun_family = AF_LOCAL; + snprintf(addr->sun_path, sizeof(addr->sun_path), "%s/%s", runtime_dir, + socket_name); + + char* lock_addr = sl_xasprintf("%s%s", addr->sun_path, LOCK_SUFFIX); + + *lock_fd = open(lock_addr, O_CREAT | O_CLOEXEC, + (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP)); + errno_assert(*lock_fd >= 0); + + rv = flock(*lock_fd, LOCK_EX | LOCK_NB); + if (rv < 0) { + fprintf(stderr, + "error: unable to lock %s, is another compositor running?\n", + lock_addr); + return rv; + } + free(lock_addr); + + struct stat sock_stat; + rv = stat(addr->sun_path, &sock_stat); + if (rv >= 0) { + if (sock_stat.st_mode & (S_IWUSR | S_IWGRP)) + unlink(addr->sun_path); + } else { + errno_assert(errno == ENOENT); + } + + *sock_fd = socket(PF_LOCAL, SOCK_STREAM, 0); + errno_assert(*sock_fd >= 0); + + rv = bind(*sock_fd, (struct sockaddr*)addr, + offsetof(struct sockaddr_un, sun_path) + strlen(addr->sun_path)); + errno_assert(rv >= 0); + + rv = listen(*sock_fd, 128); + errno_assert(rv >= 0); + + return 0; +} + +int sl_run_parent(int argc, + char** argv, + sl_context* ctx, + const char* socket_name, + const char* peer_cmd_prefix) { + struct sockaddr_un addr; + int lock_fd; + int sock_fd; + int rv; + + rv = sl_open_wayland_socket(socket_name, &addr, &lock_fd, &sock_fd); + if (rv < 0) + return EXIT_FAILURE; + + // Spawn optional child process before we notify systemd that we're ready + // to accept connections. WAYLAND_DISPLAY will be set but any attempt to + // connect to this socket at this time will fail. + if (ctx->runprog && ctx->runprog[0]) { + int pid = fork(); + errno_assert(pid != -1); + if (pid == 0) { + setenv("WAYLAND_DISPLAY", socket_name, 1); + sl_execvp(ctx->runprog[0], ctx->runprog, -1); + _exit(EXIT_FAILURE); + } + while (waitpid(-1, NULL, WNOHANG) != pid) + continue; + } + + if (ctx->sd_notify) + sl_sd_notify(ctx->sd_notify); + + struct sigaction sa; + sa.sa_handler = sl_sigchld_handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + rv = sigaction(SIGCHLD, &sa, NULL); + errno_assert(rv >= 0); + + do { + struct ucred ucred; + socklen_t length = sizeof(addr); + + int client_fd = accept(sock_fd, (struct sockaddr*)&addr, &length); + if (client_fd < 0) { + fprintf(stderr, "error: failed to accept: %m\n"); + continue; + } + + ucred.pid = -1; + length = sizeof(ucred); + rv = getsockopt(client_fd, SOL_SOCKET, SO_PEERCRED, &ucred, &length); + + int pid = fork(); + errno_assert(pid != -1); + if (pid == 0) { + char* client_fd_str; + char* peer_pid_str; + char* peer_cmd_prefix_str; + char const* args[64]; + int i = 0; + + close(sock_fd); + close(lock_fd); + + if (peer_cmd_prefix) { + peer_cmd_prefix_str = sl_xasprintf("%s", peer_cmd_prefix); + + i = sl_parse_cmd_prefix(peer_cmd_prefix_str, 32, args); + if (i > 32) { + fprintf(stderr, "error: too many arguments in cmd prefix: %d\n", i); + i = 0; + } + } + + args[i++] = argv[0]; + peer_pid_str = sl_xasprintf("--peer-pid=%d", ucred.pid); + args[i++] = peer_pid_str; + client_fd_str = sl_xasprintf("--client-fd=%d", client_fd); + args[i++] = client_fd_str; + + // forward some flags. + for (int j = 1; j < argc; ++j) { + char* arg = argv[j]; + if (strstr(arg, "--display") == arg || strstr(arg, "--scale") == arg || + strstr(arg, "--direct-scale") == arg || + strstr(arg, "--accelerators") == arg || + strstr(arg, "--windowed-accelerators") == arg || + strstr(arg, "--drm-device") == arg || + strstr(arg, "--support-damage-buffer") == arg || + strstr(arg, "--vm-identififer") == arg) { + args[i++] = arg; + } + } + + args[i++] = NULL; + + execvp(args[0], const_cast(args)); + _exit(EXIT_FAILURE); + } + close(client_fd); + } while (1); + + // Control should never reach here. + assert(false); +} + +void sl_spawn_xwayland(sl_context* ctx, + wl_event_loop* event_loop, + int wayland_socket_fd, + const char* xwayland_cmd_prefix, + const char* xwayland_path, + int xdisplay, + const char* xauth_path, + const char* xfont_path, + const char* xwayland_gl_driver_path, + const char* glamor) { + int ds[2]; + int rv; + // Xwayland display ready socket. + rv = socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, ds); + errno_assert(!rv); + + ctx->display_ready_event_source.reset( + wl_event_loop_add_fd(event_loop, ds[0], WL_EVENT_READABLE, + sl_handle_display_ready_event, ctx)); + + int wm[2]; + // X connection to Xwayland. + rv = socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, wm); + errno_assert(!rv); + + ctx->wm_fd = wm[0]; + + int pid = fork(); + errno_assert(pid != -1); + if (pid == 0) { + char const* args[64]; + int i = 0; + + if (xwayland_cmd_prefix) { + char* xwayland_cmd_prefix_str = sl_xasprintf("%s", xwayland_cmd_prefix); + + i = sl_parse_cmd_prefix(xwayland_cmd_prefix_str, 32, args); + if (i > 32) { + fprintf(stderr, "error: too many arguments in cmd prefix: %d\n", i); + i = 0; + } + } + + args[i++] = sl_xasprintf("%s", xwayland_path ?: XWAYLAND_PATH); + + int fd = dup(ds[1]); + char* display_fd_str = sl_xasprintf("%d", fd); + fd = dup(wm[1]); + char* wm_fd_str = sl_xasprintf("%d", fd); + + if (xdisplay > 0) { + args[i++] = sl_xasprintf(":%d", xdisplay); + } + args[i++] = "-nolisten"; + args[i++] = "tcp"; + args[i++] = "-rootless"; + // Use software rendering unless we have a DRM device and glamor is + // enabled. + if (!ctx->drm_device || !glamor || !strcmp(glamor, "0")) + args[i++] = "-shm"; + args[i++] = "-displayfd"; + args[i++] = display_fd_str; + args[i++] = "-wm"; + args[i++] = wm_fd_str; + if (xauth_path) { + args[i++] = "-auth"; + args[i++] = sl_xasprintf("%s", xauth_path); + } + if (xfont_path) { + args[i++] = "-fp"; + args[i++] = sl_xasprintf("%s", xfont_path); + } + args[i++] = NULL; + + // If a path is explicitly specified via command line or environment + // use that instead of the compiled in default. In either case, only + // set the environment variable if the value specified is non-empty. + if (xwayland_gl_driver_path) { + if (*xwayland_gl_driver_path) { + setenv("LIBGL_DRIVERS_PATH", xwayland_gl_driver_path, 1); + } + } else if (XWAYLAND_GL_DRIVER_PATH && *XWAYLAND_GL_DRIVER_PATH) { + setenv("LIBGL_DRIVERS_PATH", XWAYLAND_GL_DRIVER_PATH, 1); + } + + sl_execvp(args[0], const_cast(args), wayland_socket_fd); + _exit(EXIT_FAILURE); + } + close(wm[1]); + ctx->xwayland_pid = pid; +} + +int real_main(int argc, char** argv) { + struct sl_context ctx; + sl_context_init_default(&ctx); + const char* display = getenv("SOMMELIER_DISPLAY"); const char* scale = getenv("SOMMELIER_SCALE"); const char* dpi = getenv("SOMMELIER_DPI"); const char* clipboard_manager = getenv("SOMMELIER_CLIPBOARD_MANAGER"); const char* frame_color = getenv("SOMMELIER_FRAME_COLOR"); const char* dark_frame_color = getenv("SOMMELIER_DARK_FRAME_COLOR"); - const char* virtwl_device = getenv("SOMMELIER_VIRTWL_DEVICE"); - const char* drm_device = getenv("SOMMELIER_DRM_DEVICE"); + const char* support_damage_buffer = getenv("SOMMELIER_SUPPORT_DAMAGE_BUFFER"); + const char* force_drm_device = NULL; const char* glamor = getenv("SOMMELIER_GLAMOR"); - const char* shm_driver = getenv("SOMMELIER_SHM_DRIVER"); - const char* data_driver = getenv("SOMMELIER_DATA_DRIVER"); + const char* fullscreen_mode = getenv("SOMMELIER_FULLSCREEN_MODE"); const char* peer_cmd_prefix = getenv("SOMMELIER_PEER_CMD_PREFIX"); const char* xwayland_cmd_prefix = getenv("SOMMELIER_XWAYLAND_CMD_PREFIX"); const char* accelerators = getenv("SOMMELIER_ACCELERATORS"); + const char* windowed_accelerators = getenv("SOMMELIER_WINDOWED_ACCELERATORS"); const char* xwayland_path = getenv("SOMMELIER_XWAYLAND_PATH"); const char* xwayland_gl_driver_path = getenv("SOMMELIER_XWAYLAND_GL_DRIVER_PATH"); const char* xauth_path = getenv("SOMMELIER_XAUTH_PATH"); const char* xfont_path = getenv("SOMMELIER_XFONT_PATH"); + const char* vm_id = getenv("SOMMELIER_VM_IDENTIFIER"); const char* socket_name = "wayland-0"; - const char* runtime_dir; + bool noop_driver = false; struct wl_event_loop* event_loop; - struct wl_listener client_destroy_listener = {.notify = - sl_client_destroy_notify}; + struct wl_listener client_destroy_listener = {}; + client_destroy_listener.notify = sl_client_destroy_notify; int sv[2]; pid_t pid; - int virtwl_display_fd = -1; int xdisplay = -1; - int master = 0; + int parent = 0; int client_fd = -1; - int rv; int i; + // Ignore SIGUSR1 (used for trace dumping) in all child processes. + signal(SIGUSR1, SIG_IGN); + for (i = 1; i < argc; ++i) { const char* arg = argv[i]; if (strcmp(arg, "--help") == 0 || strcmp(arg, "-h") == 0 || @@ -3556,16 +3723,12 @@ int main(int argc, char** argv) { printf("Version: %s\n", SOMMELIER_VERSION); return EXIT_SUCCESS; } - if (strstr(arg, "--master") == arg) { - master = 1; + if (strstr(arg, "--parent") == arg) { + parent = 1; } else if (strstr(arg, "--socket") == arg) { socket_name = sl_arg_value(arg); } else if (strstr(arg, "--display") == arg) { display = sl_arg_value(arg); - } else if (strstr(arg, "--shm-driver") == arg) { - shm_driver = sl_arg_value(arg); - } else if (strstr(arg, "--data-driver") == arg) { - data_driver = sl_arg_value(arg); } else if (strstr(arg, "--peer-pid") == arg) { ctx.peer_pid = atoi(sl_arg_value(arg)); } else if (strstr(arg, "--peer-cmd-prefix") == arg) { @@ -3574,12 +3737,21 @@ int main(int argc, char** argv) { xwayland_cmd_prefix = sl_arg_value(arg); } else if (strstr(arg, "--client-fd") == arg) { client_fd = atoi(sl_arg_value(arg)); + } else if (strstr(arg, "--direct-scale") == arg) { + ctx.use_direct_scale = true; } else if (strstr(arg, "--scale") == arg) { scale = sl_arg_value(arg); } else if (strstr(arg, "--dpi") == arg) { dpi = sl_arg_value(arg); } else if (strstr(arg, "--accelerators") == arg) { accelerators = sl_arg_value(arg); + } else if (strstr(arg, "--windowed-accelerators") == arg) { + windowed_accelerators = sl_arg_value(arg); + } else if (strstr(arg, "--vm-identifier") == arg) { + vm_id = sl_arg_value(arg); + } else if (strstr(arg, "--application-id-x11-property") == arg) { + // NB: Must be parsed before --application-id. + ctx.application_id_property_name = sl_arg_value(arg); } else if (strstr(arg, "--application-id") == arg) { ctx.application_id = sl_arg_value(arg); } else if (strstr(arg, "-X") == arg) { @@ -3602,16 +3774,34 @@ int main(int argc, char** argv) { frame_color = sl_arg_value(arg); } else if (strstr(arg, "--dark-frame-color") == arg) { dark_frame_color = sl_arg_value(arg); - } else if (strstr(arg, "--virtwl-device") == arg) { - virtwl_device = sl_arg_value(arg); - } else if (strstr(arg, "--drm-device") == arg) { - drm_device = sl_arg_value(arg); + } else if (strstr(arg, "--force-drm-device") == arg) { + force_drm_device = sl_arg_value(arg); + } else if (strstr(arg, "--no-support-damage-buffer") == arg) { + support_damage_buffer = "0"; } else if (strstr(arg, "--glamor") == arg) { glamor = "1"; + } else if (strstr(arg, "--fullscreen-mode") == arg) { + fullscreen_mode = sl_arg_value(arg); } else if (strstr(arg, "--x-auth") == arg) { xauth_path = sl_arg_value(arg); } else if (strstr(arg, "--x-font-path") == arg) { xfont_path = sl_arg_value(arg); + } else if (strstr(arg, "--timing-filename") == arg) { + ctx.timing = new Timing(sl_arg_value(arg)); + } else if (strstr(arg, "--explicit-fence") == arg) { + ctx.use_explicit_fence = true; + } else if (strstr(arg, "--enable-xshape") == arg) { + ctx.enable_xshape = true; + } else if (strstr(arg, "--virtgpu-channel") == arg) { + ctx.use_virtgpu_channel = true; + } else if (strstr(arg, "--noop-driver") == arg) { + noop_driver = true; +#ifdef PERFETTO_TRACING + } else if (strstr(arg, "--trace-filename") == arg) { + ctx.trace_filename = sl_arg_value(arg); + } else if (strstr(arg, "--trace-system") == arg) { + ctx.trace_system = true; +#endif } else if (arg[0] == '-') { if (strcmp(arg, "--") == 0) { ctx.runprog = &argv[i + 1]; @@ -3626,152 +3816,25 @@ int main(int argc, char** argv) { } } - runtime_dir = getenv("XDG_RUNTIME_DIR"); - if (!runtime_dir) { - fprintf(stderr, "error: XDG_RUNTIME_DIR not set in the environment\n"); - return EXIT_FAILURE; + if (vm_id && strlen(vm_id)) { + // Some GuestOS instances of sommelier will be started with vm_id set to a + // container_token from /dev/.container_token. + // The vm_id may be empty if the token or file does not yet exist. + // Keep the vm_id as default (termina) if this occurs. + ctx.vm_id = vm_id; } - if (master) { - char* lock_addr; - struct sockaddr_un addr; - struct sigaction sa; - struct stat sock_stat; - int lock_fd; - int sock_fd; + if (ctx.application_id && ctx.vm_id) { + fprintf(stderr, "warning: --application-id overrides --vm-identifier\n"); + } + if (ctx.application_id && ctx.application_id_property_name) { + fprintf(stderr, + "warning: --application-id overrides" + " --application-id-x11-property\n"); + } - addr.sun_family = AF_LOCAL; - snprintf(addr.sun_path, sizeof(addr.sun_path), "%s/%s", runtime_dir, - socket_name); - - lock_addr = sl_xasprintf("%s%s", addr.sun_path, LOCK_SUFFIX); - - lock_fd = open(lock_addr, O_CREAT | O_CLOEXEC, - (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP)); - assert(lock_fd >= 0); - - rv = flock(lock_fd, LOCK_EX | LOCK_NB); - if (rv < 0) { - fprintf(stderr, - "error: unable to lock %s, is another compositor running?\n", - lock_addr); - return EXIT_FAILURE; - } - free(lock_addr); - - rv = stat(addr.sun_path, &sock_stat); - if (rv >= 0) { - if (sock_stat.st_mode & (S_IWUSR | S_IWGRP)) - unlink(addr.sun_path); - } else { - assert(errno == ENOENT); - } - - sock_fd = socket(PF_LOCAL, SOCK_STREAM, 0); - assert(sock_fd >= 0); - - rv = bind(sock_fd, (struct sockaddr*)&addr, - offsetof(struct sockaddr_un, sun_path) + strlen(addr.sun_path)); - assert(rv >= 0); - - rv = listen(sock_fd, 128); - assert(rv >= 0); - - // Spawn optional child process before we notify systemd that we're ready - // to accept connections. WAYLAND_DISPLAY will be set but any attempt to - // connect to this socket at this time will fail. - if (ctx.runprog && ctx.runprog[0]) { - pid = fork(); - assert(pid != -1); - if (pid == 0) { - setenv("WAYLAND_DISPLAY", socket_name, 1); - sl_execvp(ctx.runprog[0], ctx.runprog, -1); - _exit(EXIT_FAILURE); - } - while (waitpid(-1, NULL, WNOHANG) != pid) - continue; - } - - if (ctx.sd_notify) - sl_sd_notify(ctx.sd_notify); - - sa.sa_handler = sl_sigchld_handler; - sigemptyset(&sa.sa_mask); - sa.sa_flags = SA_RESTART; - rv = sigaction(SIGCHLD, &sa, NULL); - assert(rv >= 0); - - do { - struct ucred ucred; - socklen_t length = sizeof(addr); - - client_fd = accept(sock_fd, (struct sockaddr*)&addr, &length); - if (client_fd < 0) { - fprintf(stderr, "error: failed to accept: %m\n"); - continue; - } - - ucred.pid = -1; - length = sizeof(ucred); - rv = getsockopt(client_fd, SOL_SOCKET, SO_PEERCRED, &ucred, &length); - - pid = fork(); - assert(pid != -1); - if (pid == 0) { - char* client_fd_str; - char* peer_pid_str; - //char* peer_cmd_prefix_str; - char* args[64]; - int i = 0, j; - - close(sock_fd); - close(lock_fd); - - if (!peer_cmd_prefix) - peer_cmd_prefix = PEER_CMD_PREFIX; - - /* - if (peer_cmd_prefix) { - peer_cmd_prefix_str = sl_xasprintf("%s", peer_cmd_prefix); - - i = sl_parse_cmd_prefix(peer_cmd_prefix_str, 32, args); - if (i > 32) { - fprintf(stderr, "error: too many arguments in cmd prefix: %d\n", i); - i = 0; - } - } - */ - - args[i++] = argv[0]; - peer_pid_str = sl_xasprintf("--peer-pid=%d", ucred.pid); - args[i++] = peer_pid_str; - client_fd_str = sl_xasprintf("--client-fd=%d", client_fd); - args[i++] = client_fd_str; - - // forward some flags. - for (j = 1; j < argc; ++j) { - char* arg = argv[j]; - if (strstr(arg, "--display") == arg || - strstr(arg, "--scale") == arg || - strstr(arg, "--accelerators") == arg || - strstr(arg, "--virtwl-device") == arg || - strstr(arg, "--drm-device") == arg || - strstr(arg, "--shm-driver") == arg || - strstr(arg, "--data-driver") == arg) { - args[i++] = arg; - } - } - - args[i++] = NULL; - - execvp(args[0], args); - _exit(EXIT_FAILURE); - } - close(client_fd); - } while (1); - - // Control should never reach here. - assert(false); + if (parent) { + return sl_run_parent(argc, argv, &ctx, socket_name, peer_cmd_prefix); } if (client_fd == -1) { @@ -3792,7 +3855,13 @@ int main(int argc, char** argv) { if (scale) { ctx.desired_scale = atof(scale); // Round to integer scale until we detect wp_viewporter support. - ctx.scale = MIN(MAX_SCALE, MAX(MIN_SCALE, round(ctx.desired_scale))); + // In direct scale mode, take the scale value as is. + + if (ctx.use_direct_scale) { + ctx.scale = ctx.desired_scale; + } else { + ctx.scale = MIN(MAX_SCALE, MAX(MIN_SCALE, round(ctx.desired_scale))); + } } if (!frame_color) @@ -3813,72 +3882,56 @@ int main(int argc, char** argv) { ctx.dark_frame_color = 0xff000000 | (r << 16) | (g << 8) | (b << 0); } + ctx.support_damage_buffer = support_damage_buffer == nullptr || + strcmp(support_damage_buffer, "1") == 0; + + if (fullscreen_mode) { + if (strcmp(fullscreen_mode, "immersive") == 0) { + ctx.fullscreen_mode = ZAURA_SURFACE_FULLSCREEN_MODE_IMMERSIVE; + } else if (strcmp(fullscreen_mode, "plain") == 0) { + ctx.fullscreen_mode = ZAURA_SURFACE_FULLSCREEN_MODE_PLAIN; + } else { + fprintf(stderr, "error: unrecognised --fullscreen-mode\n"); + sl_print_usage(); + return EXIT_FAILURE; + } + } + // Handle broken pipes without signals that kill the entire process. signal(SIGPIPE, SIG_IGN); ctx.host_display = wl_display_create(); assert(ctx.host_display); - event_loop = wl_display_get_event_loop(ctx.host_display); - - if (!virtwl_device) - virtwl_device = VIRTWL_DEVICE; - - if (virtwl_device) { - struct virtwl_ioctl_new new_ctx = { - .type = VIRTWL_IOCTL_NEW_CTX, - .fd = -1, - .flags = 0, - .size = 0, - }; - - ctx.virtwl_fd = open(virtwl_device, O_RDWR); - if (ctx.virtwl_fd == -1) { - fprintf(stderr, "error: could not open %s (%s)\n", virtwl_device, - strerror(errno)); - return EXIT_FAILURE; - } - - // We use a virtwl context unless display was explicitly specified. - // WARNING: It's critical that we never call wl_display_roundtrip - // as we're not spawning a new thread to handle forwarding. Calling - // wl_display_roundtrip will cause a deadlock. - if (!display) { - int vws[2]; - - // Connection to virtwl channel. - rv = socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, vws); - assert(!rv); - - ctx.virtwl_socket_fd = vws[0]; - virtwl_display_fd = vws[1]; - - rv = ioctl(ctx.virtwl_fd, VIRTWL_IOCTL_NEW, &new_ctx); - if (rv) { - fprintf(stderr, "error: failed to create virtwl context: %s\n", - strerror(errno)); - return EXIT_FAILURE; - } - - ctx.virtwl_ctx_fd = new_ctx.fd; - - ctx.virtwl_socket_event_source = wl_event_loop_add_fd( - event_loop, ctx.virtwl_socket_fd, WL_EVENT_READABLE, - sl_handle_virtwl_socket_event, &ctx); - ctx.virtwl_ctx_event_source = - wl_event_loop_add_fd(event_loop, ctx.virtwl_ctx_fd, WL_EVENT_READABLE, - sl_handle_virtwl_ctx_event, &ctx); - } + if (noop_driver) { + ctx.channel = NULL; + } else if (ctx.use_virtgpu_channel) { + ctx.channel = new VirtGpuChannel(); + } else { + ctx.channel = new VirtWaylandChannel(); } - if (drm_device) { - int drm_fd = open(drm_device, O_RDWR | O_CLOEXEC); + event_loop = wl_display_get_event_loop(ctx.host_display); + if (!sl_context_init_wayland_channel(&ctx, event_loop, display)) { + return EXIT_FAILURE; + } + + int drm_fd = -1; + char* drm_device = NULL; + if (force_drm_device != NULL) { + // Use DRM device specified on the command line. + drm_fd = open(force_drm_device, O_RDWR | O_CLOEXEC); if (drm_fd == -1) { - fprintf(stderr, "error: could not open %s (%s)\n", drm_device, + fprintf(stderr, "error: could not open %s (%s)\n", force_drm_device, strerror(errno)); return EXIT_FAILURE; } - + drm_device = strdup(force_drm_device); + } else { + // Enumerate render nodes to find the virtio_gpu device. + drm_fd = open_virtgpu(&drm_device); + } + if (drm_fd >= 0) { ctx.gbm = gbm_create_device(drm_fd); if (!ctx.gbm) { fprintf(stderr, "error: couldn't get display device\n"); @@ -3888,104 +3941,30 @@ int main(int argc, char** argv) { ctx.drm_device = drm_device; } - if (!shm_driver) - shm_driver = ctx.xwayland ? XWAYLAND_SHM_DRIVER : SHM_DRIVER; - - if (shm_driver) { - if (strcmp(shm_driver, "dmabuf") == 0) { - if (!ctx.drm_device) { - fprintf(stderr, "error: need drm device for dmabuf driver\n"); - return EXIT_FAILURE; - } - ctx.shm_driver = SHM_DRIVER_DMABUF; - } else if (strcmp(shm_driver, "virtwl") == 0 || - strcmp(shm_driver, "virtwl-dmabuf") == 0) { - if (ctx.virtwl_fd == -1) { - fprintf(stderr, "error: need device for virtwl driver\n"); - return EXIT_FAILURE; - } - ctx.shm_driver = strcmp(shm_driver, "virtwl") ? SHM_DRIVER_VIRTWL_DMABUF - : SHM_DRIVER_VIRTWL; - // Check for compatibility with virtwl-dmabuf. - if (ctx.shm_driver == SHM_DRIVER_VIRTWL_DMABUF) { - struct virtwl_ioctl_new new_dmabuf = { - .type = VIRTWL_IOCTL_NEW_DMABUF, - .fd = -1, - .flags = 0, - .dmabuf = - { - .width = 0, - .height = 0, - .format = 0, - }, - }; - if (ioctl(ctx.virtwl_fd, VIRTWL_IOCTL_NEW, &new_dmabuf) == -1 && - errno == ENOTTY) { - fprintf(stderr, - "warning: virtwl-dmabuf driver not supported by host, using " - "virtwl instead\n"); - ctx.shm_driver = SHM_DRIVER_VIRTWL; - } else if (new_dmabuf.fd >= 0) { - // Close the returned dmabuf fd in case the invalid dmabuf metadata - // given above actually manages to return an fd successfully. - close(new_dmabuf.fd); - } - } - } - } else if (ctx.drm_device) { - ctx.shm_driver = SHM_DRIVER_DMABUF; - } else if (ctx.virtwl_fd != -1) { - ctx.shm_driver = SHM_DRIVER_VIRTWL_DMABUF; - } - - if (data_driver) { - if (strcmp(data_driver, "virtwl") == 0) { - if (ctx.virtwl_fd == -1) { - fprintf(stderr, "error: need device for virtwl driver\n"); - return EXIT_FAILURE; - } - ctx.data_driver = DATA_DRIVER_VIRTWL; - } - } else if (ctx.virtwl_fd != -1) { - ctx.data_driver = DATA_DRIVER_VIRTWL; - } - - // Use well known values for DPI by default with Xwayland. - if (!dpi && ctx.xwayland) - dpi = "72,96,160,240,320,480"; - wl_array_init(&ctx.dpi); if (dpi) { char* str = strdup(dpi); - char* token = strtok(str, ","); + char* token = strtok(str, ","); // NOLINT(runtime/threadsafe_fn) int* p; while (token) { - p = wl_array_add(&ctx.dpi, sizeof *p); + p = static_cast(wl_array_add(&ctx.dpi, sizeof *p)); assert(p); *p = MAX(MIN_DPI, MIN(atoi(token), MAX_DPI)); - token = strtok(NULL, ","); + token = strtok(NULL, ","); // NOLINT(runtime/threadsafe_fn) } free(str); } - if (ctx.runprog || ctx.xwayland) { - // Wayland connection from client. - rv = socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sv); - assert(!rv); - - client_fd = sv[0]; - } - // The success of this depends on xkb-data being installed. - ctx.xkb_context = xkb_context_new(0); + ctx.xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); if (!ctx.xkb_context) { fprintf(stderr, "error: xkb_context_new failed. xkb-data missing?\n"); return EXIT_FAILURE; } - if (virtwl_display_fd != -1) { - ctx.display = wl_display_connect_to_fd(virtwl_display_fd); + if (ctx.virtwl_display_fd != -1) { + ctx.display = wl_display_connect_to_fd(ctx.virtwl_display_fd); } else { if (display == NULL) display = getenv("WAYLAND_DISPLAY"); @@ -4000,177 +3979,46 @@ int main(int argc, char** argv) { return EXIT_FAILURE; } - wl_list_init(&ctx.accelerators); - wl_list_init(&ctx.registries); - wl_list_init(&ctx.globals); - wl_list_init(&ctx.outputs); - wl_list_init(&ctx.seats); - wl_list_init(&ctx.windows); - wl_list_init(&ctx.unpaired_windows); - wl_list_init(&ctx.host_outputs); - wl_list_init(&ctx.selection_data_source_send_pending); + if (!sl_parse_accelerators(&ctx.accelerators, accelerators)) + return EXIT_FAILURE; + if (!sl_parse_accelerators(&ctx.windowed_accelerators, windowed_accelerators)) + return EXIT_FAILURE; - // Parse the list of accelerators that should be reserved by the - // compositor. Format is "|MODIFIERS|KEYSYM", where MODIFIERS is a - // list of modifier names (E.g. ) and KEYSYM is an - // XKB key symbol name (E.g Delete). - if (accelerators) { - uint32_t modifiers = 0; - - while (*accelerators) { - if (*accelerators == ',') { - accelerators++; - } else if (*accelerators == '<') { - if (strncmp(accelerators, "", 9) == 0) { - modifiers |= CONTROL_MASK; - accelerators += 9; - } else if (strncmp(accelerators, "", 5) == 0) { - modifiers |= ALT_MASK; - accelerators += 5; - } else if (strncmp(accelerators, "", 7) == 0) { - modifiers |= SHIFT_MASK; - accelerators += 7; - } else { - fprintf(stderr, "error: invalid modifier\n"); - return EXIT_FAILURE; - } - } else { - struct sl_accelerator* accelerator; - const char* end = strchrnul(accelerators, ','); - char* name = strndup(accelerators, end - accelerators); - - accelerator = malloc(sizeof(*accelerator)); - accelerator->modifiers = modifiers; - accelerator->symbol = - xkb_keysym_from_name(name, XKB_KEYSYM_CASE_INSENSITIVE); - if (accelerator->symbol == XKB_KEY_NoSymbol) { - fprintf(stderr, "error: invalid key symbol\n"); - return EXIT_FAILURE; - } - - wl_list_insert(&ctx.accelerators, &accelerator->link); - - modifiers = 0; - accelerators = end; - free(name); - } - } - } - - ctx.display_event_source = + ctx.display_event_source.reset( wl_event_loop_add_fd(event_loop, wl_display_get_fd(ctx.display), - WL_EVENT_READABLE, sl_handle_event, &ctx); + WL_EVENT_READABLE, sl_handle_event, &ctx)); wl_registry_add_listener(wl_display_get_registry(ctx.display), &sl_registry_listener, &ctx); + if (ctx.runprog || ctx.xwayland) { + // Wayland connection from client. + int rv = socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sv); + errno_assert(!rv); + client_fd = sv[0]; + } + ctx.client = wl_client_create(ctx.host_display, client_fd); // Replace the core display implementation. This is needed in order to // implement sync handler properly. - sl_set_display_implementation(&ctx); + sl_set_display_implementation(&ctx, ctx.client); if (ctx.runprog || ctx.xwayland) { - ctx.sigchld_event_source = - wl_event_loop_add_signal(event_loop, SIGCHLD, sl_handle_sigchld, &ctx); - - // Unset DISPLAY to prevent X clients from connecting to an existing X - // server when X forwarding is not enabled. - unsetenv("DISPLAY"); - // Set WAYLAND_DISPLAY to value that is guaranteed to not point to a - // valid wayland compositor socket name. Resetting WAYLAND_DISPLAY is - // insufficient as clients will attempt to connect to wayland-0 if - // it's not set. - setenv("WAYLAND_DISPLAY", ".", 1); + ctx.sigchld_event_source.reset( + wl_event_loop_add_signal(event_loop, SIGCHLD, sl_handle_sigchld, &ctx)); if (ctx.xwayland) { - int ds[2], wm[2]; - - // Xwayland display ready socket. - rv = socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, ds); - assert(!rv); - - ctx.display_ready_event_source = - wl_event_loop_add_fd(event_loop, ds[0], WL_EVENT_READABLE, - sl_handle_display_ready_event, &ctx); - - // X connection to Xwayland. - rv = socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, wm); - assert(!rv); - - ctx.wm_fd = wm[0]; - - pid = fork(); - assert(pid != -1); - if (pid == 0) { - char* display_fd_str; - char* wm_fd_str; - char* xwayland_cmd_prefix_str; - char* args[64]; - int i = 0; - int fd; - - if (xwayland_cmd_prefix) { - xwayland_cmd_prefix_str = sl_xasprintf("%s", xwayland_cmd_prefix); - - i = sl_parse_cmd_prefix(xwayland_cmd_prefix_str, 32, args); - if (i > 32) { - fprintf(stderr, "error: too many arguments in cmd prefix: %d\n", i); - i = 0; - } - } - - args[i++] = sl_xasprintf("%s", xwayland_path ?: XWAYLAND_PATH); - - fd = dup(ds[1]); - display_fd_str = sl_xasprintf("%d", fd); - fd = dup(wm[1]); - wm_fd_str = sl_xasprintf("%d", fd); - - if (xdisplay > 0) { - args[i++] = sl_xasprintf(":%d", xdisplay); - } - args[i++] = "-nolisten"; - args[i++] = "tcp"; - args[i++] = "-rootless"; - // Use software rendering unless we have a DRM device and glamor is - // enabled. - if (!ctx.drm_device || !glamor || !strcmp(glamor, "0")) - args[i++] = "-shm"; - args[i++] = "-displayfd"; - args[i++] = display_fd_str; - args[i++] = "-wm"; - args[i++] = wm_fd_str; - if (xauth_path) { - args[i++] = "-auth"; - args[i++] = sl_xasprintf("%s", xauth_path); - } - if (xfont_path) { - args[i++] = "-fp"; - args[i++] = sl_xasprintf("%s", xfont_path); - } - args[i++] = NULL; - - // If a path is explicitly specified via command line or environment - // use that instead of the compiled in default. In either case, only - // set the environment variable if the value specified is non-empty. - if (xwayland_gl_driver_path) { - if (*xwayland_gl_driver_path) { - setenv("LIBGL_DRIVERS_PATH", xwayland_gl_driver_path, 1); - } - } else if (XWAYLAND_GL_DRIVER_PATH && *XWAYLAND_GL_DRIVER_PATH) { - setenv("LIBGL_DRIVERS_PATH", XWAYLAND_GL_DRIVER_PATH, 1); - } - - sl_execvp(args[0], args, sv[1]); - _exit(EXIT_FAILURE); - } - close(wm[1]); - ctx.xwayland_pid = pid; + sl_spawn_xwayland(&ctx, event_loop, sv[1], xwayland_cmd_prefix, + xwayland_path, xdisplay, xauth_path, xfont_path, + xwayland_gl_driver_path, glamor); } else { pid = fork(); - assert(pid != -1); + errno_assert(pid != -1); if (pid == 0) { + // Unset DISPLAY to prevent X clients from connecting to an existing X + // server when X forwarding is not enabled. + unsetenv("DISPLAY"); sl_execvp(ctx.runprog[0], ctx.runprog, sv[1]); _exit(EXIT_FAILURE); } @@ -4179,6 +4027,25 @@ int main(int argc, char** argv) { close(sv[1]); } + // Attempt to enable tracing. This could be called earlier but would rather + // spawn all children first. + const bool tracing_needed = ctx.trace_filename || ctx.trace_system; + if (tracing_needed) { + initialize_tracing(ctx.trace_filename, ctx.trace_system); + enable_tracing(!ctx.trace_system); + } + + // Trigger trace and timing log dumps when USR1 signals are received + if (tracing_needed || ctx.timing) { + ctx.sigusr1_event_source.reset( + wl_event_loop_add_signal(event_loop, SIGUSR1, sl_handle_sigusr1, &ctx)); + } + + // Initialize timing log values. + if (ctx.timing) { + ctx.timing->RecordStartTime(); + } + wl_client_add_destroy_listener(ctx.client, &client_destroy_listener); do { @@ -4192,7 +4059,13 @@ int main(int argc, char** argv) { } if (wl_display_flush(ctx.display) < 0) return EXIT_FAILURE; - } while (wl_event_loop_dispatch(event_loop, -1) != -1); + + if (wl_event_loop_dispatch(event_loop, -1) == -1) { + // Ignore EINTR or sommelier will exit when attached by strace or gdb. + if (errno != EINTR) + return EXIT_FAILURE; + } + } while (1); return EXIT_SUCCESS; -} +} // NOLINT(readability/fn_size) diff --git a/sommelier/sommelier.h b/sommelier/sommelier.h index 00debc6..45140fa 100644 --- a/sommelier/sommelier.h +++ b/sommelier/sommelier.h @@ -1,31 +1,34 @@ -// Copyright 2018 The Chromium OS Authors. All rights reserved. +// Copyright 2018 The ChromiumOS Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef VM_TOOLS_SOMMELIER_SOMMELIER_H_ #define VM_TOOLS_SOMMELIER_SOMMELIER_H_ +#include +#include #include #include #include #include #include -#include "config.h" +#include "compositor/sommelier-mmap.h" // NOLINT(build/include_directory) +#include "sommelier-ctx.h" // NOLINT(build/include_directory) +#include "sommelier-global.h" // NOLINT(build/include_directory) +#include "sommelier-util.h" // NOLINT(build/include_directory) +#include "sommelier-window.h" // NOLINT(build/include_directory) +#include "weak-resource-ptr.h" // NOLINT(build/include_directory) #define SOMMELIER_VERSION "0.20" -#define MIN(a, b) (((a) < (b)) ? (a) : (b)) -#define MAX(a, b) (((a) > (b)) ? (a) : (b)) - #define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) -#define UNUSED(x) ((void)(x)) - #define CONTROL_MASK (1 << 0) #define ALT_MASK (1 << 1) #define SHIFT_MASK (1 << 2) + struct sl_global; struct sl_compositor; struct sl_shm; @@ -39,148 +42,41 @@ struct sl_aura_shell; struct sl_viewporter; struct sl_linux_dmabuf; struct sl_keyboard_extension; +struct sl_text_input_extension; struct sl_text_input_manager; struct sl_relative_pointer_manager; struct sl_pointer_constraints; struct sl_window; +struct sl_host_surface; +struct sl_host_output; struct zaura_shell; struct zcr_keyboard_extension_v1; +struct zxdg_output_manager_v1; -enum { - ATOM_WM_S0, - ATOM_WM_PROTOCOLS, - ATOM_WM_STATE, - ATOM_WM_CHANGE_STATE, - ATOM_WM_DELETE_WINDOW, - ATOM_WM_TAKE_FOCUS, - ATOM_WM_CLIENT_LEADER, - ATOM_WL_SURFACE_ID, - ATOM_UTF8_STRING, - ATOM_MOTIF_WM_HINTS, - ATOM_NET_FRAME_EXTENTS, - ATOM_NET_STARTUP_ID, - ATOM_NET_SUPPORTING_WM_CHECK, - ATOM_NET_WM_NAME, - ATOM_NET_WM_MOVERESIZE, - ATOM_NET_WM_STATE, - ATOM_NET_WM_STATE_FULLSCREEN, - ATOM_NET_WM_STATE_MAXIMIZED_VERT, - ATOM_NET_WM_STATE_MAXIMIZED_HORZ, - ATOM_CLIPBOARD, - ATOM_CLIPBOARD_MANAGER, - ATOM_TARGETS, - ATOM_TIMESTAMP, - ATOM_TEXT, - ATOM_INCR, - ATOM_WL_SELECTION, - ATOM_GTK_THEME_VARIANT, - ATOM_LAST = ATOM_GTK_THEME_VARIANT, -}; +#ifdef GAMEPAD_SUPPORT +struct sl_gamepad; +struct sl_gaming_input_manager; +struct zcr_gaming_input_v2; +#endif -enum { - SHM_DRIVER_NOOP, - SHM_DRIVER_DMABUF, - SHM_DRIVER_VIRTWL, - SHM_DRIVER_VIRTWL_DMABUF, -}; +class WaylandChannel; -enum { - DATA_DRIVER_NOOP, - DATA_DRIVER_VIRTWL, -}; +extern const struct wl_registry_listener sl_registry_listener; -struct sl_context { - char** runprog; - struct wl_display* display; - struct wl_display* host_display; - struct wl_client* client; - struct sl_compositor* compositor; - struct sl_subcompositor* subcompositor; - struct sl_shm* shm; - struct sl_shell* shell; - struct sl_data_device_manager* data_device_manager; - struct sl_xdg_shell* xdg_shell; - struct sl_aura_shell* aura_shell; - struct sl_viewporter* viewporter; - struct sl_linux_dmabuf* linux_dmabuf; - struct sl_keyboard_extension* keyboard_extension; - struct sl_text_input_manager* text_input_manager; - struct sl_relative_pointer_manager* relative_pointer_manager; - struct sl_pointer_constraints* pointer_constraints; - struct wl_list outputs; - struct wl_list seats; - struct wl_event_source* display_event_source; - struct wl_event_source* display_ready_event_source; - struct wl_event_source* sigchld_event_source; - struct wl_array dpi; - int shm_driver; - int data_driver; - int wm_fd; - int virtwl_fd; - int virtwl_ctx_fd; - int virtwl_socket_fd; - struct wl_event_source* virtwl_ctx_event_source; - struct wl_event_source* virtwl_socket_event_source; - const char* drm_device; - struct gbm_device* gbm; - int xwayland; - pid_t xwayland_pid; - pid_t child_pid; - pid_t peer_pid; - struct xkb_context* xkb_context; - struct wl_list accelerators; - struct wl_list registries; - struct wl_list globals; - struct wl_list host_outputs; - int next_global_id; - xcb_connection_t* connection; - struct wl_event_source* connection_event_source; - const xcb_query_extension_reply_t* xfixes_extension; - xcb_screen_t* screen; - xcb_window_t window; - struct wl_list windows, unpaired_windows; - struct sl_window* host_focus_window; - int needs_set_input_focus; - double desired_scale; - double scale; - const char* application_id; - int exit_with_child; - const char* sd_notify; - int clipboard_manager; - uint32_t frame_color; - uint32_t dark_frame_color; - struct sl_host_seat* default_seat; - xcb_window_t selection_window; - xcb_window_t selection_owner; - int selection_incremental_transfer; - xcb_selection_request_event_t selection_request; - xcb_timestamp_t selection_timestamp; - struct wl_data_device* selection_data_device; - struct sl_data_offer* selection_data_offer; - struct sl_data_source* selection_data_source; - int selection_data_source_send_fd; - struct wl_list selection_data_source_send_pending; - struct wl_event_source* selection_send_event_source; - xcb_get_property_reply_t* selection_property_reply; - int selection_property_offset; - struct wl_event_source* selection_event_source; - xcb_atom_t selection_data_type; - struct wl_array selection_data; - int selection_data_offer_receive_fd; - int selection_data_ack_pending; - union { - const char* name; - xcb_intern_atom_cookie_t cookie; - xcb_atom_t value; - } atoms[ATOM_LAST + 1]; - xcb_visualid_t visual_ids[256]; - xcb_colormap_t colormaps[256]; -}; +// Exported for testing. +void sl_registry_handler(void* data, + struct wl_registry* registry, + uint32_t id, + const char* interface, + uint32_t version); + +// We require a host compositor supporting at least this wl_compositor version. +constexpr uint32_t kMinHostWlCompositorVersion = + WL_SURFACE_SET_BUFFER_SCALE_SINCE_VERSION; struct sl_compositor { struct sl_context* ctx; uint32_t id; - uint32_t version; struct sl_global* host_global; struct wl_compositor* internal; }; @@ -206,15 +102,18 @@ struct sl_host_pointer { struct wl_resource* resource; struct wl_pointer* proxy; struct wl_resource* focus_resource; + struct sl_host_surface* focus_surface; struct wl_listener focus_resource_listener; uint32_t focus_serial; + uint32_t time; + wl_fixed_t axis_delta[2]; + int32_t axis_discrete[2]; }; struct sl_relative_pointer_manager { struct sl_context* ctx; uint32_t id; struct sl_global* host_global; - struct zwp_relative_pointer_manager_v1* internal; }; struct sl_viewport { @@ -237,26 +136,44 @@ struct sl_host_surface { struct wl_resource* resource; struct wl_surface* proxy; struct wp_viewport* viewport; + struct wl_buffer* proxy_buffer; uint32_t contents_width; uint32_t contents_height; + uint32_t contents_shm_format; int32_t contents_scale; + int32_t contents_x_offset; + int32_t contents_y_offset; + double xdg_scale_x; + double xdg_scale_y; + bool scale_round_on_x; + bool scale_round_on_y; struct wl_list contents_viewport; struct sl_mmap* contents_shm_mmap; + bool contents_shaped; + pixman_region32_t contents_shape; int has_role; int has_output; + int has_own_scale; + int32_t cached_logical_width; + int32_t cached_logical_height; uint32_t last_event_serial; struct sl_output_buffer* current_buffer; + struct zwp_linux_surface_synchronization_v1* surface_sync; struct wl_list released_buffers; struct wl_list busy_buffers; + WeakResourcePtr output; }; +MAP_STRUCTS(wl_surface, sl_host_surface); struct sl_host_region { struct sl_context* ctx; struct wl_resource* resource; struct wl_region* proxy; }; +MAP_STRUCTS(wl_region, sl_host_region); struct sl_host_buffer { + struct sl_context* ctx; struct wl_resource* resource; struct wl_buffer* proxy; uint32_t width; @@ -264,6 +181,7 @@ struct sl_host_buffer { struct sl_mmap* shm_mmap; uint32_t shm_format; struct sl_sync_point* sync_point; + bool is_drm; }; struct sl_data_source_send_request { @@ -290,19 +208,30 @@ struct sl_output { uint32_t id; uint32_t version; struct sl_global* host_global; + struct sl_host_output* host_output; struct wl_list link; }; +struct sl_xdg_output_manager { + struct sl_context* ctx; + uint32_t id; + uint32_t version; + struct zxdg_output_manager_v1* internal; +}; + struct sl_host_output { struct sl_context* ctx; struct wl_resource* resource; struct wl_output* proxy; + struct zxdg_output_v1* zxdg_output; struct zaura_output* aura_output; int internal; int x; int y; int physical_width; int physical_height; + int scaled_physical_width; + int scaled_physical_height; int subpixel; char* make; char* model; @@ -316,14 +245,40 @@ struct sl_host_output { int preferred_scale; int device_scale_factor; int expecting_scale; + bool expecting_logical_size; + + // The scaling factors for direct mode + // virt_scale: Used to translate from physical space to virtual space + // xdg_scale: Used to translate from virtual space to logical space + // + // The logical space is defined by the host. It will be retrieved through + // the xdg_output_manager interface. + // + // All spaces, and by consequence all scale factors, will be unique to each + // particular output. + // + // For more details, see sommelier-transform.h + + double virt_scale_x; + double virt_scale_y; + double xdg_scale_x; + double xdg_scale_y; + int virt_x; + int virt_y; + int32_t logical_width; + int32_t logical_height; + int32_t logical_x; + int32_t logical_y; struct wl_list link; }; +MAP_STRUCTS(wl_output, sl_host_output); struct sl_host_seat { struct sl_seat* seat; struct wl_resource* resource; struct wl_seat* proxy; }; +MAP_STRUCTS(wl_seat, sl_host_seat); struct sl_accelerator { struct wl_list link; @@ -356,14 +311,27 @@ struct sl_text_input_manager { struct sl_context* ctx; uint32_t id; struct sl_global* host_global; - struct zwp_text_input_manager_v1* internal; + struct sl_global* host_x11_global; }; +struct sl_text_input_extension { + struct sl_context* ctx; + uint32_t id; + struct sl_global* host_global; +}; + +#ifdef GAMEPAD_SUPPORT +struct sl_gaming_input_manager { + struct sl_context* ctx; + uint32_t id; + struct zcr_gaming_input_v2* internal; +}; +#endif + struct sl_pointer_constraints { struct sl_context* ctx; uint32_t id; struct sl_global* host_global; - struct zwp_pointer_constraints_v1* internal; }; struct sl_viewporter { @@ -377,7 +345,7 @@ struct sl_xdg_shell { struct sl_context* ctx; uint32_t id; struct sl_global* host_global; - struct zxdg_shell_v6* internal; + struct xdg_wm_base* internal; }; struct sl_aura_shell { @@ -396,6 +364,12 @@ struct sl_linux_dmabuf { struct zwp_linux_dmabuf_v1* internal; }; +struct sl_linux_explicit_synchronization { + struct sl_context* ctx; + uint32_t id; + struct zwp_linux_explicit_synchronization_v1* internal; +}; + struct sl_global { struct sl_context* ctx; const struct wl_interface* interface; @@ -412,22 +386,6 @@ struct sl_host_registry { struct wl_list link; }; -typedef void (*sl_begin_end_access_func_t)(int fd); - -struct sl_mmap { - int refcount; - int fd; - void* addr; - size_t size; - size_t bpp; - size_t num_planes; - size_t offset[2]; - size_t stride[2]; - size_t y_ss[2]; - sl_begin_end_access_func_t begin_write; - sl_begin_end_access_func_t end_write; - struct wl_resource* buffer_resource; -}; typedef void (*sl_sync_func_t)(struct sl_context* ctx, struct sl_sync_point* sync_point); @@ -437,65 +395,30 @@ struct sl_sync_point { sl_sync_func_t sync; }; -struct sl_config { - uint32_t serial; - uint32_t mask; - uint32_t values[5]; - uint32_t states_length; - uint32_t states[3]; -}; - -struct sl_window { +#ifdef GAMEPAD_SUPPORT +struct sl_host_gamepad { struct sl_context* ctx; - xcb_window_t id; - xcb_window_t frame_id; - uint32_t host_surface_id; - int unpaired; - int x; - int y; - int width; - int height; - int border_width; - int depth; - int managed; - int realized; - int activated; - int maximized; - int allow_resize; - xcb_window_t transient_for; - xcb_window_t client_leader; - int decorated; - char* name; - char* clazz; - char* startup_id; - int dark_frame; - uint32_t size_flags; - int min_width; - int min_height; - int max_width; - int max_height; - struct sl_config next_config; - struct sl_config pending_config; - struct zxdg_surface_v6* xdg_surface; - struct zxdg_toplevel_v6* xdg_toplevel; - struct zxdg_popup_v6* xdg_popup; - struct zaura_surface* aura_surface; + int state; + struct libevdev* ev_dev; + struct libevdev_uinput* uinput_dev; + bool axes_quirk; struct wl_list link; }; +#endif -struct sl_host_buffer* sl_create_host_buffer(struct wl_client* client, +struct sl_host_buffer* sl_create_host_buffer(struct sl_context* ctx, + struct wl_client* client, uint32_t id, struct wl_buffer* proxy, int32_t width, - int32_t height); - -struct sl_global* sl_global_create(struct sl_context* ctx, - const struct wl_interface* interface, - int version, - void* data, - wl_global_bind_func_t bind); + int32_t height, + bool is_drm); struct sl_global* sl_compositor_global_create(struct sl_context* ctx); +void sl_compositor_init_context(struct sl_context* ctx, + struct wl_registry* registry, + uint32_t id, + uint32_t version); size_t sl_shm_bpp_for_shm_format(uint32_t format); @@ -528,24 +451,16 @@ struct sl_global* sl_gtk_shell_global_create(struct sl_context* ctx); struct sl_global* sl_drm_global_create(struct sl_context* ctx); +struct sl_global* sl_text_input_extension_global_create(struct sl_context* ctx); + struct sl_global* sl_text_input_manager_global_create(struct sl_context* ctx); +struct sl_global* sl_text_input_x11_global_create(struct sl_context* ctx); + struct sl_global* sl_pointer_constraints_global_create(struct sl_context* ctx); -void sl_set_display_implementation(struct sl_context* ctx); - -struct sl_mmap* sl_mmap_create(int fd, - size_t size, - size_t bpp, - size_t num_planes, - size_t offset0, - size_t stride0, - size_t offset1, - size_t stride1, - size_t y_ss0, - size_t y_ss1); -struct sl_mmap* sl_mmap_ref(struct sl_mmap* map); -void sl_mmap_unref(struct sl_mmap* map); +void sl_set_display_implementation(struct sl_context* ctx, + struct wl_client* client); struct sl_sync_point* sl_sync_point_create(int fd); void sl_sync_point_destroy(struct sl_sync_point* sync_point); @@ -557,9 +472,48 @@ void sl_restack_windows(struct sl_context* ctx, uint32_t focus_resource_id); void sl_roundtrip(struct sl_context* ctx); -int sl_process_pending_configure_acks(struct sl_window* window, - struct sl_host_surface* host_surface); -void sl_window_update(struct sl_window* window); +struct sl_window* sl_lookup_window(struct sl_context* ctx, xcb_window_t id); +int sl_is_our_window(struct sl_context* ctx, xcb_window_t id); + +// Exported for testing +void sl_handle_destroy_notify(struct sl_context* ctx, + xcb_destroy_notify_event_t* event); +void sl_handle_reparent_notify(struct sl_context* ctx, + xcb_reparent_notify_event_t* event); +void sl_handle_map_request(struct sl_context* ctx, + xcb_map_request_event_t* event); +void sl_handle_unmap_notify(struct sl_context* ctx, + xcb_unmap_notify_event_t* event); +void sl_handle_configure_request(struct sl_context* ctx, + xcb_configure_request_event_t* event); +void sl_handle_property_notify(struct sl_context* ctx, + xcb_property_notify_event_t* event); +void sl_create_window(struct sl_context* ctx, + xcb_window_t id, + int x, + int y, + int width, + int height, + int border_width); +void sl_handle_client_message(struct sl_context* ctx, + xcb_client_message_event_t* event); +void sl_handle_focus_in(struct sl_context* ctx, xcb_focus_in_event_t* event); + +uint32_t sl_drm_format_for_shm_format(int format); +int sl_shm_format_for_drm_format(uint32_t drm_format); + +#ifdef GAMEPAD_SUPPORT +void sl_gaming_seat_add_listener(struct sl_context* ctx); +#endif + +bool sl_client_supports_interface(const sl_context* ctx, + const wl_client* client, + const wl_interface* interface); + +#define sl_array_for_each(pos, array) \ + for (pos = static_cast((array)->data); \ + (const char*)pos < ((const char*)(array)->data + (array)->size); \ + (pos)++) #endif // VM_TOOLS_SOMMELIER_SOMMELIER_H_ diff --git a/sommelier/sommelier_test.cc b/sommelier/sommelier_test.cc new file mode 100644 index 0000000..c253cb2 --- /dev/null +++ b/sommelier/sommelier_test.cc @@ -0,0 +1,688 @@ +// Copyright 2021 The ChromiumOS Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "sommelier.h" // NOLINT(build/include_directory) +#include "virtualization/wayland_channel.h" // NOLINT(build/include_directory) + +#include "aura-shell-client-protocol.h" // NOLINT(build/include_directory) +#include "xdg-shell-client-protocol.h" // NOLINT(build/include_directory) + +// Help gtest print Wayland message streams on expectation failure. +// +// This is defined in the test file mostly to avoid the main program depending +// on and merely for testing purposes. Also, it doesn't +// print the entire struct, just the data buffer, so it's not a complete +// representation of the object. +std::ostream& operator<<(std::ostream& os, const WaylandSendReceive& w) { + // Partially decode the data buffer. The content of messages is not decoded, + // except their object ID and opcode. + size_t i = 0; + while (i < w.data_size) { + uint32_t object_id = *reinterpret_cast(w.data + i); + uint32_t second_word = *reinterpret_cast(w.data + i + 4); + uint16_t message_size_in_bytes = second_word >> 16; + uint16_t opcode = second_word & 0xffff; + os << "[object ID " << object_id << ", opcode " << opcode << ", length " + << message_size_in_bytes; + + uint16_t size = MIN(message_size_in_bytes, w.data_size - i); + if (size > sizeof(uint32_t) * 2) { + os << ", args=["; + for (int j = sizeof(uint32_t) * 2; j < size; ++j) { + char byte = w.data[i + j]; + if (isprint(byte)) { + os << byte; + } else { + os << "\\" << static_cast(byte); + } + } + os << "]"; + } + os << "]"; + i += message_size_in_bytes; + } + if (i != w.data_size) { + os << "[WARNING: " << (w.data_size - i) << "undecoded trailing bytes]"; + } + + return os; +} + +namespace vm_tools { +namespace sommelier { + +using ::testing::_; +using ::testing::AllOf; +using ::testing::DoAll; +using ::testing::NiceMock; +using ::testing::PrintToString; +using ::testing::Return; +using ::testing::SetArgPointee; + +class MockWaylandChannel : public WaylandChannel { + public: + MockWaylandChannel() {} + + MOCK_METHOD(int32_t, init, ()); + MOCK_METHOD(bool, supports_dmabuf, ()); + MOCK_METHOD(int32_t, + create_context, + (int& out_socket_fd)); // NOLINT(runtime/references) + MOCK_METHOD(int32_t, + create_pipe, + (int& out_pipe_fd)); // NOLINT(runtime/references) + MOCK_METHOD(int32_t, send, (const struct WaylandSendReceive& send)); + MOCK_METHOD( + int32_t, + handle_channel_event, + (enum WaylandChannelEvent & event_type, // NOLINT(runtime/references) + struct WaylandSendReceive& receive, // NOLINT(runtime/references) + int& out_read_pipe)); // NOLINT(runtime/references) + + MOCK_METHOD(int32_t, + allocate, + (const struct WaylandBufferCreateInfo& create_info, + struct WaylandBufferCreateOutput& + create_output)); // NOLINT(runtime/references) + MOCK_METHOD(int32_t, sync, (int dmabuf_fd, uint64_t flags)); + MOCK_METHOD(int32_t, + handle_pipe, + (int read_fd, + bool readable, + bool& hang_up)); // NOLINT(runtime/references) + MOCK_METHOD(size_t, max_send_size, ()); + + protected: + ~MockWaylandChannel() override {} +}; + +// Match a WaylandSendReceive buffer containing exactly one Wayland message +// with given object ID and opcode. +MATCHER_P2(ExactlyOneMessage, + object_id, + opcode, + std::string("exactly one Wayland message ") + + (negation ? "not for" : "for") + " object ID " + + PrintToString(object_id) + ", opcode " + PrintToString(opcode)) { + const struct WaylandSendReceive& send = arg; + if (send.data_size < sizeof(uint32_t) * 2) { + // Malformed packet (too short) + return false; + } + + uint32_t actual_object_id = *reinterpret_cast(send.data); + uint32_t second_word = *reinterpret_cast(send.data + 4); + uint16_t message_size_in_bytes = second_word >> 16; + uint16_t actual_opcode = second_word & 0xffff; + + // ID and opcode must match expectation, and we must see exactly one message + // with the indicated length. + return object_id == actual_object_id && opcode == actual_opcode && + message_size_in_bytes == send.data_size; +}; + +// Match a WaylandSendReceive buffer containing a string. +// TODO(cpelling): This is currently very naive; it doesn't respect +// boundaries between messages or their arguments. Fix me. +MATCHER_P(AnyMessageContainsString, + str, + std::string("a Wayland message containing string ") + str) { + const struct WaylandSendReceive& send = arg; + size_t prefix_len = sizeof(uint32_t) * 2; + std::string data_as_str(reinterpret_cast(send.data + prefix_len), + send.data_size - prefix_len); + + return data_as_str.find(str) != std::string::npos; +} + +// Fixture for tests which exercise only Wayland functionality. +class WaylandTest : public ::testing::Test { + public: + void SetUp() override { + ON_CALL(mock_wayland_channel_, create_context(_)).WillByDefault(Return(0)); + ON_CALL(mock_wayland_channel_, max_send_size()) + .WillByDefault(Return(DEFAULT_BUFFER_SIZE)); + EXPECT_CALL(mock_wayland_channel_, init).Times(1); + sl_context_init_default(&ctx); + ctx.host_display = wl_display_create(); + assert(ctx.host_display); + + ctx.channel = &mock_wayland_channel_; + EXPECT_TRUE(sl_context_init_wayland_channel( + &ctx, wl_display_get_event_loop(ctx.host_display), false)); + + InitContext(); + Connect(); + } + + void TearDown() override { + // Process any pending messages before the test exits. + Pump(); + + // TODO(cpelling): Destroy context and any created windows? + } + + // Flush and dispatch Wayland client calls to the mock host. + // + // Called by default in TearDown(), but you can also trigger it midway + // through the test. + // + // If you call `EXPECT_CALL(mock_wayland_channel_, send)` before Pump(), the + // expectations won't trigger until the Pump() call. + // + // Conversely, calling Pump() before + // `EXPECT_CALL(mock_wayland_channel_, send)` is useful to flush out + // init messages not relevant to your test case. + void Pump() { + wl_display_flush(ctx.display); + wl_event_loop_dispatch(wl_display_get_event_loop(ctx.host_display), 0); + } + + protected: + // Allow subclasses to customize the context prior to Connect(). + virtual void InitContext() {} + + // Set up the Wayland connection, compositor and registry. + virtual void Connect() { + ctx.display = wl_display_connect_to_fd(ctx.virtwl_display_fd); + wl_registry* registry = wl_display_get_registry(ctx.display); + + sl_compositor_init_context(&ctx, registry, 0, kMinHostWlCompositorVersion); + EXPECT_NE(ctx.compositor, nullptr); + + // Fake the Wayland server advertising globals. + uint32_t id = 1; + sl_registry_handler(&ctx, registry, id++, "xdg_wm_base", + XDG_WM_BASE_GET_XDG_SURFACE_SINCE_VERSION); + sl_registry_handler(&ctx, registry, id++, "zaura_shell", + ZAURA_SURFACE_SET_FULLSCREEN_MODE_SINCE_VERSION); + } + + testing::NiceMock mock_wayland_channel_; + sl_context ctx; +}; + +// Fixture for unit tests which exercise both Wayland and X11 functionality. +class X11Test : public WaylandTest { + public: + void InitContext() override { + WaylandTest::InitContext(); + ctx.xwayland = 1; + } + + void Connect() override { + WaylandTest::Connect(); + ctx.connection = xcb_connect(NULL, NULL); + } + + virtual sl_window* CreateWindowWithoutRole() { + xcb_window_t window_id = 1; + sl_create_window(&ctx, window_id, 0, 0, 800, 600, 0); + sl_window* window = sl_lookup_window(&ctx, window_id); + EXPECT_NE(window, nullptr); + return window; + } + + virtual sl_window* CreateToplevelWindow() { + sl_window* window = CreateWindowWithoutRole(); + wl_surface* surface = + wl_compositor_create_surface(ctx.compositor->internal); + window->host_surface_id = + wl_proxy_get_id(reinterpret_cast(surface)); + + window->xdg_surface = + xdg_wm_base_get_xdg_surface(ctx.xdg_shell->internal, surface); + window->xdg_toplevel = xdg_surface_get_toplevel(window->xdg_surface); + + window->aura_surface = + zaura_shell_get_aura_surface(ctx.aura_shell->internal, surface); + return window; + } +}; + +namespace { +uint32_t XdgToplevelId(sl_window* window) { + return wl_proxy_get_id(reinterpret_cast(window->xdg_toplevel)); +} + +uint32_t AuraSurfaceId(sl_window* window) { + return wl_proxy_get_id(reinterpret_cast(window->aura_surface)); +} +} // namespace + +TEST_F(WaylandTest, CanCommitToEmptySurface) { + wl_surface* surface = wl_compositor_create_surface(ctx.compositor->internal); + wl_surface_commit(surface); +} + +TEST_F(X11Test, TogglesFullscreenOnWmStateFullscreen) { + // Arrange: Create an xdg_toplevel surface. Initially it's not fullscreen. + sl_window* window = CreateToplevelWindow(); + uint32_t xdg_toplevel_id = XdgToplevelId(window); + EXPECT_EQ(window->fullscreen, 0); + Pump(); // exclude pending messages from EXPECT_CALL()s below + + // Act: Pretend the window is owned by an X11 client requesting fullscreen. + // Sommelier receives the XCB_CLIENT_MESSAGE request due to its role as the + // X11 window manager. For test purposes, we skip creating a real X11 + // connection and just call directly into the relevant handler. + xcb_client_message_event_t event; + event.response_type = XCB_CLIENT_MESSAGE; + event.format = 32; + event.window = window->id; + event.type = ctx.atoms[ATOM_NET_WM_STATE].value; + event.data.data32[0] = NET_WM_STATE_ADD; + event.data.data32[1] = ctx.atoms[ATOM_NET_WM_STATE_FULLSCREEN].value; + event.data.data32[2] = 0; + event.data.data32[3] = 0; + event.data.data32[4] = 0; + sl_handle_client_message(&ctx, &event); + + // Assert: Sommelier records the fullscreen state. + EXPECT_EQ(window->fullscreen, 1); + // Assert: Sommelier forwards the fullscreen request to Exo. + EXPECT_CALL( + mock_wayland_channel_, + send(ExactlyOneMessage(xdg_toplevel_id, XDG_TOPLEVEL_SET_FULLSCREEN))) + .RetiresOnSaturation(); + Pump(); + + // Act: Pretend the fictitious X11 client requests non-fullscreen. + event.data.data32[0] = NET_WM_STATE_REMOVE; + sl_handle_client_message(&ctx, &event); + + // Assert: Sommelier records the fullscreen state. + EXPECT_EQ(window->fullscreen, 0); + // Assert: Sommelier forwards the unfullscreen request to Exo. + EXPECT_CALL( + mock_wayland_channel_, + send(ExactlyOneMessage(xdg_toplevel_id, XDG_TOPLEVEL_UNSET_FULLSCREEN))) + .RetiresOnSaturation(); +} + +TEST_F(X11Test, TogglesMaximizeOnWmStateMaximize) { + // Arrange: Create an xdg_toplevel surface. Initially it's not maximized. + sl_window* window = CreateToplevelWindow(); + uint32_t xdg_toplevel_id = XdgToplevelId(window); + EXPECT_EQ(window->maximized, 0); + Pump(); // exclude pending messages from EXPECT_CALL()s below + + // Act: Pretend an X11 client owns the surface, and requests to maximize it. + xcb_client_message_event_t event; + event.response_type = XCB_CLIENT_MESSAGE; + event.format = 32; + event.window = window->id; + event.type = ctx.atoms[ATOM_NET_WM_STATE].value; + event.data.data32[0] = NET_WM_STATE_ADD; + event.data.data32[1] = ctx.atoms[ATOM_NET_WM_STATE_MAXIMIZED_HORZ].value; + event.data.data32[2] = ctx.atoms[ATOM_NET_WM_STATE_MAXIMIZED_VERT].value; + event.data.data32[3] = 0; + event.data.data32[4] = 0; + sl_handle_client_message(&ctx, &event); + + // Assert: Sommelier records the maximized state + forwards to Exo. + EXPECT_EQ(window->maximized, 1); + EXPECT_CALL( + mock_wayland_channel_, + send(ExactlyOneMessage(xdg_toplevel_id, XDG_TOPLEVEL_SET_MAXIMIZED))) + .RetiresOnSaturation(); + Pump(); + + // Act: Pretend the fictitious X11 client requests to unmaximize. + event.data.data32[0] = NET_WM_STATE_REMOVE; + sl_handle_client_message(&ctx, &event); + + // Assert: Sommelier records the unmaximized state + forwards to Exo. + EXPECT_EQ(window->maximized, 0); + EXPECT_CALL( + mock_wayland_channel_, + send(ExactlyOneMessage(xdg_toplevel_id, XDG_TOPLEVEL_UNSET_MAXIMIZED))) + .RetiresOnSaturation(); + Pump(); +} + +TEST_F(X11Test, CanEnterFullscreenIfAlreadyMaximized) { + // Arrange + sl_window* window = CreateToplevelWindow(); + uint32_t xdg_toplevel_id = XdgToplevelId(window); + Pump(); // exclude pending messages from EXPECT_CALL()s below + + // Act: Pretend an X11 client owns the surface, and requests to maximize it. + xcb_client_message_event_t event; + event.response_type = XCB_CLIENT_MESSAGE; + event.format = 32; + event.window = window->id; + event.type = ctx.atoms[ATOM_NET_WM_STATE].value; + event.data.data32[0] = NET_WM_STATE_ADD; + event.data.data32[1] = ctx.atoms[ATOM_NET_WM_STATE_MAXIMIZED_HORZ].value; + event.data.data32[2] = ctx.atoms[ATOM_NET_WM_STATE_MAXIMIZED_VERT].value; + event.data.data32[3] = 0; + event.data.data32[4] = 0; + sl_handle_client_message(&ctx, &event); + + // Assert: Sommelier records the maximized state + forwards to Exo. + EXPECT_EQ(window->maximized, 1); + EXPECT_CALL( + mock_wayland_channel_, + send(ExactlyOneMessage(xdg_toplevel_id, XDG_TOPLEVEL_SET_MAXIMIZED))) + .RetiresOnSaturation(); + Pump(); + + // Act: Pretend the X11 client requests fullscreen. + xcb_client_message_event_t fsevent; + fsevent.response_type = XCB_CLIENT_MESSAGE; + fsevent.format = 32; + fsevent.window = window->id; + fsevent.type = ctx.atoms[ATOM_NET_WM_STATE].value; + fsevent.data.data32[0] = NET_WM_STATE_ADD; + fsevent.data.data32[1] = 0; + fsevent.data.data32[2] = ctx.atoms[ATOM_NET_WM_STATE_FULLSCREEN].value; + fsevent.data.data32[3] = 0; + fsevent.data.data32[4] = 0; + sl_handle_client_message(&ctx, &fsevent); + + // Assert: Sommelier records the fullscreen state + forwards to Exo. + EXPECT_EQ(window->fullscreen, 1); + EXPECT_CALL( + mock_wayland_channel_, + send(ExactlyOneMessage(xdg_toplevel_id, XDG_TOPLEVEL_SET_FULLSCREEN))) + .RetiresOnSaturation(); + Pump(); +} + +TEST_F(X11Test, UpdatesApplicationIdFromContext) { + sl_window* window = CreateToplevelWindow(); + Pump(); + + window->managed = 1; // pretend window is mapped + // Should be ignored; global app id from context takes priority. + window->app_id_property = "org.chromium.guest_os.termina.appid.from.window"; + + ctx.application_id = "org.chromium.guest_os.termina.appid.from.context"; + sl_update_application_id(&ctx, window); + EXPECT_CALL(mock_wayland_channel_, + send(AllOf(ExactlyOneMessage(AuraSurfaceId(window), + ZAURA_SURFACE_SET_APPLICATION_ID), + AnyMessageContainsString(ctx.application_id)))) + .RetiresOnSaturation(); + Pump(); +} + +TEST_F(X11Test, UpdatesApplicationIdFromWindow) { + sl_window* window = CreateToplevelWindow(); + Pump(); + + window->managed = 1; // pretend window is mapped + window->app_id_property = "org.chromium.guest_os.termina.appid.from.window"; + sl_update_application_id(&ctx, window); + EXPECT_CALL(mock_wayland_channel_, + send(AllOf(ExactlyOneMessage(AuraSurfaceId(window), + ZAURA_SURFACE_SET_APPLICATION_ID), + AnyMessageContainsString(window->app_id_property)))) + .RetiresOnSaturation(); + Pump(); +} + +TEST_F(X11Test, UpdatesApplicationIdFromWindowClass) { + sl_window* window = CreateToplevelWindow(); + Pump(); + + window->managed = 1; // pretend window is mapped + window->clazz = strdup("very_classy"); // not const, can't use a literal + ctx.vm_id = "testvm"; + sl_update_application_id(&ctx, window); + EXPECT_CALL( + mock_wayland_channel_, + send(AllOf(ExactlyOneMessage(AuraSurfaceId(window), + ZAURA_SURFACE_SET_APPLICATION_ID), + AnyMessageContainsString( + "org.chromium.guest_os.testvm.wmclass.very_classy")))) + .RetiresOnSaturation(); + Pump(); + free(window->clazz); +} + +TEST_F(X11Test, UpdatesApplicationIdFromClientLeader) { + sl_window* window = CreateToplevelWindow(); + Pump(); + + window->managed = 1; // pretend window is mapped + window->client_leader = window->id; + ctx.vm_id = "testvm"; + sl_update_application_id(&ctx, window); + EXPECT_CALL(mock_wayland_channel_, + send(AllOf(ExactlyOneMessage(AuraSurfaceId(window), + ZAURA_SURFACE_SET_APPLICATION_ID), + AnyMessageContainsString( + "org.chromium.guest_os.testvm.wmclientleader.")))) + .RetiresOnSaturation(); + Pump(); +} + +TEST_F(X11Test, UpdatesApplicationIdFromXid) { + sl_window* window = CreateToplevelWindow(); + Pump(); + + window->managed = 1; // pretend window is mapped + ctx.vm_id = "testvm"; + sl_update_application_id(&ctx, window); + EXPECT_CALL(mock_wayland_channel_, + send(AllOf(ExactlyOneMessage(AuraSurfaceId(window), + ZAURA_SURFACE_SET_APPLICATION_ID), + AnyMessageContainsString( + "org.chromium.guest_os.testvm.xid.")))) + .RetiresOnSaturation(); + Pump(); +} + +TEST_F(X11Test, NonExistentWindowDoesNotCrash) { + // This test is testing cases where sl_lookup_window returns NULL + + // sl_handle_destroy_notify + xcb_destroy_notify_event_t destroy_event; + // Arrange: Use a window that does not exist. + destroy_event.window = 123; + // Act/Assert: Sommelier does not crash. + sl_handle_destroy_notify(&ctx, &destroy_event); + + // sl_handle_client_message + xcb_client_message_event_t message_event; + message_event.window = 123; + message_event.type = ctx.atoms[ATOM_WL_SURFACE_ID].value; + sl_handle_client_message(&ctx, &message_event); + message_event.type = ctx.atoms[ATOM_NET_ACTIVE_WINDOW].value; + sl_handle_client_message(&ctx, &message_event); + message_event.type = ctx.atoms[ATOM_NET_WM_MOVERESIZE].value; + sl_handle_client_message(&ctx, &message_event); + message_event.type = ctx.atoms[ATOM_NET_WM_STATE].value; + sl_handle_client_message(&ctx, &message_event); + message_event.type = ctx.atoms[ATOM_WM_CHANGE_STATE].value; + sl_handle_client_message(&ctx, &message_event); + + // sl_handle_map_request + xcb_map_request_event_t map_event; + map_event.window = 123; + sl_handle_map_request(&ctx, &map_event); + + // sl_handle_unmap_notify + xcb_unmap_notify_event_t unmap_event; + unmap_event.window = 123; + sl_handle_unmap_notify(&ctx, &unmap_event); + + // sl_handle_configure_request + xcb_configure_request_event_t configure_event; + configure_event.window = 123; + sl_handle_configure_request(&ctx, &configure_event); + + // sl_handle_focus_in + xcb_focus_in_event_t focus_event; + focus_event.event = 123; + sl_handle_focus_in(&ctx, &focus_event); + + // sl_handle_property_notify + xcb_property_notify_event_t notify_event; + notify_event.window = 123; + notify_event.atom = XCB_ATOM_WM_NAME; + sl_handle_property_notify(&ctx, ¬ify_event); + notify_event.atom = XCB_ATOM_WM_CLASS; + sl_handle_property_notify(&ctx, ¬ify_event); + notify_event.atom = ctx.application_id_property_atom; + sl_handle_property_notify(&ctx, ¬ify_event); + notify_event.atom = XCB_ATOM_WM_NORMAL_HINTS; + sl_handle_property_notify(&ctx, ¬ify_event); + notify_event.atom = XCB_ATOM_WM_HINTS; + sl_handle_property_notify(&ctx, ¬ify_event); + notify_event.atom = ATOM_MOTIF_WM_HINTS; + sl_handle_property_notify(&ctx, ¬ify_event); + notify_event.atom = ATOM_GTK_THEME_VARIANT; + sl_handle_property_notify(&ctx, ¬ify_event); + + // sl_handle_reparent_notify + // Put this one last and used a different window id as it creates a window. + xcb_reparent_notify_event_t reparent_event; + reparent_event.window = 1234; + xcb_screen_t screen; + screen.root = 1234; + ctx.screen = &screen; + reparent_event.parent = ctx.screen->root; + reparent_event.x = 0; + reparent_event.y = 0; + sl_handle_reparent_notify(&ctx, &reparent_event); +} + +#ifdef BLACK_SCREEN_FIX +TEST_F(X11Test, IconifySuppressesFullscreen) { + // Arrange: Create an xdg_toplevel surface. Initially it's not iconified. + sl_window* window = CreateToplevelWindow(); + uint32_t xdg_toplevel_id = XdgToplevelId(window); + EXPECT_EQ(window->iconified, 0); + + // Act: Pretend an X11 client owns the surface, and requests to iconify it. + xcb_client_message_event_t event; + event.response_type = XCB_CLIENT_MESSAGE; + event.format = 32; + event.window = window->id; + event.type = ctx.atoms[ATOM_WM_CHANGE_STATE].value; + event.data.data32[0] = WM_STATE_ICONIC; + sl_handle_client_message(&ctx, &event); + Pump(); + + // Assert: Sommelier records the iconified state. + EXPECT_EQ(window->iconified, 1); + + // Act: Pretend the surface is requested to be fullscreened. + event.type = ctx.atoms[ATOM_NET_WM_STATE].value; + event.data.data32[0] = NET_WM_STATE_ADD; + event.data.data32[1] = ctx.atoms[ATOM_NET_WM_STATE_FULLSCREEN].value; + event.data.data32[2] = 0; + event.data.data32[3] = 0; + event.data.data32[4] = 0; + sl_handle_client_message(&ctx, &event); + + // Assert: Sommelier should not send the fullscreen call as we are iconified. + EXPECT_CALL( + mock_wayland_channel_, + send((ExactlyOneMessage(xdg_toplevel_id, XDG_TOPLEVEL_SET_FULLSCREEN)))) + .Times(0); + Pump(); + + // Act: Pretend the surface receives focus. + xcb_focus_in_event_t focus_event; + focus_event.response_type = XCB_FOCUS_IN; + focus_event.event = window->id; + sl_handle_focus_in(&ctx, &focus_event); + + // Assert: The window is deiconified. + EXPECT_EQ(window->iconified, 0); + + // Assert: Sommelier should now send the fullscreen call. + EXPECT_CALL( + mock_wayland_channel_, + send((ExactlyOneMessage(xdg_toplevel_id, XDG_TOPLEVEL_SET_FULLSCREEN)))) + .Times(1); + Pump(); +} + +TEST_F(X11Test, IconifySuppressesUnmaximize) { + // Arrange: Create an xdg_toplevel surface. Initially it's not iconified. + sl_window* window = CreateToplevelWindow(); + uint32_t xdg_toplevel_id = XdgToplevelId(window); + EXPECT_EQ(window->iconified, 0); + + // Arrange: Maximize it. + xcb_client_message_event_t event; + event.response_type = XCB_CLIENT_MESSAGE; + event.format = 32; + event.window = window->id; + event.type = ctx.atoms[ATOM_NET_WM_STATE].value; + event.data.data32[0] = NET_WM_STATE_ADD; + event.data.data32[1] = ctx.atoms[ATOM_NET_WM_STATE_MAXIMIZED_VERT].value; + event.data.data32[2] = ctx.atoms[ATOM_NET_WM_STATE_MAXIMIZED_HORZ].value; + event.data.data32[3] = 0; + event.data.data32[4] = 0; + sl_handle_client_message(&ctx, &event); + EXPECT_EQ(window->maximized, 1); + + // Act: Pretend an X11 client owns the surface, and requests to iconify it. + event.type = ctx.atoms[ATOM_WM_CHANGE_STATE].value; + event.data.data32[0] = WM_STATE_ICONIC; + sl_handle_client_message(&ctx, &event); + Pump(); + + // Assert: Sommelier records the iconified state. + EXPECT_EQ(window->iconified, 1); + + // Act: Pretend the surface is requested to be unmaximized. + event.type = ctx.atoms[ATOM_NET_WM_STATE].value; + event.data.data32[0] = NET_WM_STATE_REMOVE; + event.data.data32[1] = ctx.atoms[ATOM_NET_WM_STATE_MAXIMIZED_VERT].value; + event.data.data32[2] = ctx.atoms[ATOM_NET_WM_STATE_MAXIMIZED_HORZ].value; + event.data.data32[3] = 0; + event.data.data32[4] = 0; + sl_handle_client_message(&ctx, &event); + + // Assert: Sommelier should not send the unmiximize call as we are iconified. + EXPECT_CALL( + mock_wayland_channel_, + send((ExactlyOneMessage(xdg_toplevel_id, XDG_TOPLEVEL_UNSET_MAXIMIZED)))) + .Times(0); + Pump(); + + // Act: Pretend the surface receives focus. + xcb_focus_in_event_t focus_event; + focus_event.response_type = XCB_FOCUS_IN; + focus_event.event = window->id; + sl_handle_focus_in(&ctx, &focus_event); + + // Assert: The window is deiconified. + EXPECT_EQ(window->iconified, 0); + + // Assert: Sommelier should now send the unmiximize call. + EXPECT_CALL( + mock_wayland_channel_, + send((ExactlyOneMessage(xdg_toplevel_id, XDG_TOPLEVEL_UNSET_MAXIMIZED)))) + .Times(1); + Pump(); +} +#endif + +} // namespace sommelier +} // namespace vm_tools + +int main(int argc, char** argv) { + testing::InitGoogleTest(&argc, argv); + testing::GTEST_FLAG(throw_on_failure) = true; + // TODO(nverne): set up logging? + return RUN_ALL_TESTS(); +} diff --git a/sommelier/virtualization/OWNERS b/sommelier/virtualization/OWNERS new file mode 100644 index 0000000..95fde25 --- /dev/null +++ b/sommelier/virtualization/OWNERS @@ -0,0 +1,2 @@ +ryanneph@google.com +zzyiwei@google.com diff --git a/sommelier/virtualization/linux-headers/virtgpu_drm.h b/sommelier/virtualization/linux-headers/virtgpu_drm.h new file mode 100644 index 0000000..a0e3bb1 --- /dev/null +++ b/sommelier/virtualization/linux-headers/virtgpu_drm.h @@ -0,0 +1,279 @@ +/* + * Copyright 2013 Red Hat + * All Rights Reserved. + * + * 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 AND/OR ITS SUPPLIERS 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 VIRTGPU_DRM_H +#define VIRTGPU_DRM_H + +#include "drm.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +/* Please note that modifications to all structs defined here are + * subject to backwards-compatibility constraints. + * + * Do not use pointers, use __u64 instead for 32 bit / 64 bit user/kernel + * compatibility Keep fields aligned to their size + */ + +#define DRM_VIRTGPU_MAP 0x01 +#define DRM_VIRTGPU_EXECBUFFER 0x02 +#define DRM_VIRTGPU_GETPARAM 0x03 +#define DRM_VIRTGPU_RESOURCE_CREATE 0x04 +#define DRM_VIRTGPU_RESOURCE_INFO 0x05 +#define DRM_VIRTGPU_TRANSFER_FROM_HOST 0x06 +#define DRM_VIRTGPU_TRANSFER_TO_HOST 0x07 +#define DRM_VIRTGPU_WAIT 0x08 +#define DRM_VIRTGPU_GET_CAPS 0x09 +#define DRM_VIRTGPU_RESOURCE_CREATE_BLOB 0x0a +#define DRM_VIRTGPU_CONTEXT_INIT 0x0b + +#define VIRTGPU_EXECBUF_FENCE_FD_IN 0x01 +#define VIRTGPU_EXECBUF_FENCE_FD_OUT 0x02 +#define VIRTGPU_EXECBUF_RING_IDX 0x04 +#define VIRTGPU_EXECBUF_FLAGS \ + (VIRTGPU_EXECBUF_FENCE_FD_IN | VIRTGPU_EXECBUF_FENCE_FD_OUT | \ + VIRTGPU_EXECBUF_RING_IDX | 0) + +struct drm_virtgpu_map { + __u64 offset; /* use for mmap system call */ + __u32 handle; + __u32 pad; +}; + +struct drm_virtgpu_execbuffer { + __u32 flags; + __u32 size; + __u64 command; /* void* */ + __u64 bo_handles; + __u32 num_bo_handles; + __s32 fence_fd; /* in/out fence fd (see VIRTGPU_EXECBUF_FENCE_FD_IN/OUT) */ + __u32 ring_idx; /* which command ring to use for fence creation */ + __u32 pad; +}; + +#define VIRTGPU_PARAM_3D_FEATURES 1 /* do we have 3D features in the hw */ +#define VIRTGPU_PARAM_CAPSET_QUERY_FIX 2 /* do we have the capset fix */ +#define VIRTGPU_PARAM_RESOURCE_BLOB 3 /* DRM_VIRTGPU_RESOURCE_CREATE_BLOB */ +#define VIRTGPU_PARAM_HOST_VISIBLE 4 /* Host blob resources are mappable */ +#define VIRTGPU_PARAM_CROSS_DEVICE 5 /* Cross virtio-device resource sharing */ +#define VIRTGPU_PARAM_CONTEXT_INIT 6 /* DRM_VIRTGPU_CONTEXT_INIT */ +#define VIRTGPU_PARAM_SUPPORTED_CAPSET_IDs 7 /* Bitmask of supported capability set ids */ + +struct drm_virtgpu_getparam { + __u64 param; + __u64 value; +}; + +/* NO_BO flags? NO resource flag? */ +/* resource flag for y_0_top */ +struct drm_virtgpu_resource_create { + __u32 target; + __u32 format; + __u32 bind; + __u32 width; + __u32 height; + __u32 depth; + __u32 array_size; + __u32 last_level; + __u32 nr_samples; + __u32 flags; + __u32 bo_handle; /* if this is set - recreate a new resource attached to this bo ? */ + __u32 res_handle; /* returned by kernel */ + __u32 size; /* validate transfer in the host */ + __u32 stride; /* validate transfer in the host */ +}; + +struct drm_virtgpu_resource_info { + __u32 bo_handle; + __u32 res_handle; + __u32 size; + __u32 blob_mem; +}; + +/* CHROMIUM */ +struct drm_virtgpu_resource_info_cros { + __u32 bo_handle; + __u32 res_handle; + __u32 size; + +/* Return res_handle and size. Return extended info (strides, num_planes, + * etc.) until chromeos-5.4 and return blob_mem since chromeos-5.10. + */ +#define VIRTGPU_RESOURCE_INFO_TYPE_DEFAULT 0 +/* Return res_handle, size, and extended info */ +#define VIRTGPU_RESOURCE_INFO_TYPE_EXTENDED 1 + union { + __u32 type; /* in, VIRTGPU_RESOURCE_INFO_TYPE_* */ + __u32 blob_mem; + __u32 stride; + __u32 strides[4]; /* strides[0] is accessible with stride. */ + }; + __u32 num_planes; + __u32 offsets[4]; + __u64 format_modifier; +}; + +struct drm_virtgpu_3d_box { + __u32 x; + __u32 y; + __u32 z; + __u32 w; + __u32 h; + __u32 d; +}; + +struct drm_virtgpu_3d_transfer_to_host { + __u32 bo_handle; + struct drm_virtgpu_3d_box box; + __u32 level; + __u32 offset; + __u32 stride; + __u32 layer_stride; +}; + +struct drm_virtgpu_3d_transfer_from_host { + __u32 bo_handle; + struct drm_virtgpu_3d_box box; + __u32 level; + __u32 offset; + __u32 stride; + __u32 layer_stride; +}; + +#define VIRTGPU_WAIT_NOWAIT 1 /* like it */ +struct drm_virtgpu_3d_wait { + __u32 handle; /* 0 is an invalid handle */ + __u32 flags; +}; + +struct drm_virtgpu_get_caps { + __u32 cap_set_id; + __u32 cap_set_ver; + __u64 addr; + __u32 size; + __u32 pad; +}; + +struct drm_virtgpu_resource_create_blob { +#define VIRTGPU_BLOB_MEM_GUEST 0x0001 +#define VIRTGPU_BLOB_MEM_HOST3D 0x0002 +#define VIRTGPU_BLOB_MEM_HOST3D_GUEST 0x0003 + +#define VIRTGPU_BLOB_FLAG_USE_MAPPABLE 0x0001 +#define VIRTGPU_BLOB_FLAG_USE_SHAREABLE 0x0002 +#define VIRTGPU_BLOB_FLAG_USE_CROSS_DEVICE 0x0004 + /* zero is invalid blob_mem */ + __u32 blob_mem; + __u32 blob_flags; + __u32 bo_handle; + __u32 res_handle; + __u64 size; + + /* + * for 3D contexts with VIRTGPU_BLOB_MEM_HOST3D_GUEST and + * VIRTGPU_BLOB_MEM_HOST3D otherwise, must be zero. + */ + __u32 pad; + __u32 cmd_size; + __u64 cmd; + __u64 blob_id; +}; + +#define VIRTGPU_CONTEXT_PARAM_CAPSET_ID 0x0001 +#define VIRTGPU_CONTEXT_PARAM_NUM_RINGS 0x0002 +#define VIRTGPU_CONTEXT_PARAM_POLL_RINGS_MASK 0x0003 +struct drm_virtgpu_context_set_param { + __u64 param; + __u64 value; +}; + +struct drm_virtgpu_context_init { + __u32 num_params; + __u32 pad; + + /* pointer to drm_virtgpu_context_set_param array */ + __u64 ctx_set_params; +}; + +/* + * Event code that's given when VIRTGPU_CONTEXT_PARAM_POLL_RINGS_MASK is in + * effect. The event size is sizeof(drm_event), since there is no additional + * payload. + */ +#define VIRTGPU_EVENT_FENCE_SIGNALED 0x90000000 + +#define DRM_IOCTL_VIRTGPU_MAP \ + DRM_IOWR(DRM_COMMAND_BASE + DRM_VIRTGPU_MAP, struct drm_virtgpu_map) + +#define DRM_IOCTL_VIRTGPU_EXECBUFFER \ + DRM_IOWR(DRM_COMMAND_BASE + DRM_VIRTGPU_EXECBUFFER,\ + struct drm_virtgpu_execbuffer) + +#define DRM_IOCTL_VIRTGPU_GETPARAM \ + DRM_IOWR(DRM_COMMAND_BASE + DRM_VIRTGPU_GETPARAM,\ + struct drm_virtgpu_getparam) + +#define DRM_IOCTL_VIRTGPU_RESOURCE_CREATE \ + DRM_IOWR(DRM_COMMAND_BASE + DRM_VIRTGPU_RESOURCE_CREATE, \ + struct drm_virtgpu_resource_create) + +#define DRM_IOCTL_VIRTGPU_RESOURCE_INFO \ + DRM_IOWR(DRM_COMMAND_BASE + DRM_VIRTGPU_RESOURCE_INFO, \ + struct drm_virtgpu_resource_info) + +/* same ioctl number as DRM_IOCTL_VIRTGPU_RESOURCE_INFO */ +#define DRM_IOCTL_VIRTGPU_RESOURCE_INFO_CROS \ + DRM_IOWR(DRM_COMMAND_BASE + DRM_VIRTGPU_RESOURCE_INFO, \ + struct drm_virtgpu_resource_info_cros) + +#define DRM_IOCTL_VIRTGPU_TRANSFER_FROM_HOST \ + DRM_IOWR(DRM_COMMAND_BASE + DRM_VIRTGPU_TRANSFER_FROM_HOST, \ + struct drm_virtgpu_3d_transfer_from_host) + +#define DRM_IOCTL_VIRTGPU_TRANSFER_TO_HOST \ + DRM_IOWR(DRM_COMMAND_BASE + DRM_VIRTGPU_TRANSFER_TO_HOST, \ + struct drm_virtgpu_3d_transfer_to_host) + +#define DRM_IOCTL_VIRTGPU_WAIT \ + DRM_IOWR(DRM_COMMAND_BASE + DRM_VIRTGPU_WAIT, \ + struct drm_virtgpu_3d_wait) + +#define DRM_IOCTL_VIRTGPU_GET_CAPS \ + DRM_IOWR(DRM_COMMAND_BASE + DRM_VIRTGPU_GET_CAPS, \ + struct drm_virtgpu_get_caps) + +#define DRM_IOCTL_VIRTGPU_RESOURCE_CREATE_BLOB \ + DRM_IOWR(DRM_COMMAND_BASE + DRM_VIRTGPU_RESOURCE_CREATE_BLOB, \ + struct drm_virtgpu_resource_create_blob) + +#define DRM_IOCTL_VIRTGPU_CONTEXT_INIT \ + DRM_IOWR(DRM_COMMAND_BASE + DRM_VIRTGPU_CONTEXT_INIT, \ + struct drm_virtgpu_context_init) + +#if defined(__cplusplus) +} +#endif + +#endif diff --git a/sommelier/linux/virtwl.h b/sommelier/virtualization/linux-headers/virtwl.h similarity index 92% rename from sommelier/linux/virtwl.h rename to sommelier/virtualization/linux-headers/virtwl.h index 9390413..baa9b34 100644 --- a/sommelier/linux/virtwl.h +++ b/sommelier/virtualization/linux-headers/virtwl.h @@ -21,6 +21,7 @@ enum virtwl_ioctl_new_type { VIRTWL_IOCTL_NEW_PIPE_WRITE, /* create a new virtwl dmabuf that is writable via the returned fd */ VIRTWL_IOCTL_NEW_DMABUF, + VIRTWL_IOCTL_NEW_CTX_NAMED, /* open a new named connection context */ }; struct virtwl_ioctl_new { @@ -42,6 +43,8 @@ struct virtwl_ioctl_new { __u32 offset1; /* return offset1 */ __u32 offset2; /* return offset2 */ } dmabuf; + /* name of socket if type == VIRTIO_WL_CMD_VFD_NEW_CTX_NAMED */ + char name[32]; }; }; diff --git a/sommelier/virtualization/virtgpu_channel.cc b/sommelier/virtualization/virtgpu_channel.cc new file mode 100644 index 0000000..f047182 --- /dev/null +++ b/sommelier/virtualization/virtgpu_channel.cc @@ -0,0 +1,839 @@ +// Copyright 2021 The ChromiumOS Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "virtgpu_cross_domain_protocol.h" // NOLINT(build/include_directory) +#include "linux-headers/virtgpu_drm.h" // NOLINT(build/include_directory) +#include "wayland_channel.h" // NOLINT(build/include_directory) + +// The size of a page for the guest kernel +#define PAGE_SIZE (getpagesize()) + +// We require six virtgpu params to use the virtgpu channel +#define REQUIRED_PARAMS_SIZE 6 + +// The capset for the virtgpu cross domain context type (defined internally +// for now) +#define CAPSET_CROSS_DOMAIN 5 + +// Constants taken from pipe_loader_drm.c in Mesa +#define DRM_NUM_NODES 63 + +// DRM Render nodes start at 128 +#define DRM_RENDER_NODE_START 128 + +#define MAX_SEND_SIZE \ + (DEFAULT_BUFFER_SIZE - sizeof(struct CrossDomainSendReceive)) +#define MAX_WRITE_SIZE \ + (DEFAULT_BUFFER_SIZE - sizeof(struct CrossDomainReadWrite)) + +struct virtgpu_param { + uint64_t param; + const char* name; + uint32_t value; +}; + +#define PARAM(x) \ + (struct virtgpu_param) { x, #x, 0 } + +int open_virtgpu(char** drm_device) { + int fd; + char* node; + drmVersionPtr drm_version; + + uint32_t num_nodes = DRM_NUM_NODES; + uint32_t min_render_node = DRM_RENDER_NODE_START; + uint32_t max_render_node = (min_render_node + num_nodes); + + for (uint32_t idx = min_render_node; idx < max_render_node; idx++) { + if (asprintf(&node, "%s/renderD%d", DRM_DIR_NAME, idx) < 0) + continue; + + fd = open(node, O_RDWR | O_CLOEXEC); + + if (fd < 0) { + free(node); + continue; + } + + drm_version = drmGetVersion(fd); + if (!drm_version) { + free(node); + close(fd); + continue; + } + + if (!strcmp(drm_version->name, "virtio_gpu")) { + drmFreeVersion(drm_version); + *drm_device = strdup(node); + free(node); + return fd; + } + + drmFreeVersion(drm_version); + free(node); + close(fd); + } + + return -1; +} + +int32_t fstat_pipe(int fd, uint32_t& inode) { + int32_t ret; + struct stat statbuf = {0}; + + ret = fstat(fd, &statbuf); + + if (ret) { + fprintf(stderr, "fstat failed\n"); + return ret; + } + + // fstat + S_ISFIFO(..) will return true for both anonymous and named pipes. + if (!S_ISFIFO(statbuf.st_mode)) { + fprintf(stderr, "expected anonymous pipe\n"); + return -EINVAL; + } + + inode = statbuf.st_ino; + return 0; +} + +VirtGpuChannel::~VirtGpuChannel() { + if (ring_addr_ != MAP_FAILED) + munmap(ring_addr_, PAGE_SIZE); + + // An unwritten rule for the DRM subsystem is a valid GEM valid must be + // non-zero. Checkout drm_gem_handle_create_tail in the kernel. + if (ring_handle_) + close_gem_handle(ring_handle_); + + if (virtgpu_ >= 0) + close(virtgpu_); +} + +int32_t VirtGpuChannel::init() { + int32_t ret; + char* drm_device = NULL; + uint32_t supports_wayland; + struct drm_virtgpu_get_caps args = {0}; + struct CrossDomainCapabilities cross_domain_caps = {0}; + + virtgpu_ = open_virtgpu(&drm_device); + if (virtgpu_ < 0) { + fprintf(stderr, "failed to open virtgpu\n"); + return -errno; + } + + // Not needed by the VirtGpuChannel. + free(drm_device); + + struct virtgpu_param params[REQUIRED_PARAMS_SIZE] = { + PARAM(VIRTGPU_PARAM_3D_FEATURES), + PARAM(VIRTGPU_PARAM_CAPSET_QUERY_FIX), + PARAM(VIRTGPU_PARAM_RESOURCE_BLOB), + PARAM(VIRTGPU_PARAM_HOST_VISIBLE), + PARAM(VIRTGPU_PARAM_CONTEXT_INIT), + PARAM(VIRTGPU_PARAM_SUPPORTED_CAPSET_IDs), + }; + + for (uint32_t i = 0; i < REQUIRED_PARAMS_SIZE; i++) { + struct drm_virtgpu_getparam get_param = {0}; + + get_param.param = params[i].param; + get_param.value = (uint64_t)(uintptr_t)¶ms[i].value; + ret = drmIoctl(virtgpu_, DRM_IOCTL_VIRTGPU_GETPARAM, &get_param); + if (ret < 0) { + fprintf(stderr, "DRM_IOCTL_VIRTGPU_GET_PARAM failed with %s\n", + strerror(errno)); + close(virtgpu_); + virtgpu_ = -1; + return -EINVAL; + } + + if (params[i].param == VIRTGPU_PARAM_SUPPORTED_CAPSET_IDs) { + if ((params[i].value & (1 << CAPSET_CROSS_DOMAIN)) == 0) + return -ENOTSUP; + } + } + + args.cap_set_id = CAPSET_CROSS_DOMAIN; + args.size = sizeof(struct CrossDomainCapabilities); + args.addr = (unsigned long long)&cross_domain_caps; + + ret = drmIoctl(virtgpu_, DRM_IOCTL_VIRTGPU_GET_CAPS, &args); + if (ret) { + fprintf(stderr, "DRM_IOCTL_VIRTGPU_GET_CAPS failed with %s\n", + strerror(errno)); + return ret; + } + + if (cross_domain_caps.supports_dmabuf) + supports_dmabuf_ = true; + + supports_wayland = cross_domain_caps.supported_channels & + (1 << CROSS_DOMAIN_CHANNEL_TYPE_WAYLAND); + + if (!supports_wayland) { + fprintf(stderr, "Wayland support not present on host.\n"); + return -ENOTSUP; + } + + return 0; +} + +bool VirtGpuChannel::supports_dmabuf(void) { + return supports_dmabuf_; +} + +int32_t VirtGpuChannel::create_context(int& out_channel_fd) { + int ret; + struct drm_virtgpu_map map = {0}; + struct drm_virtgpu_context_init init = {0}; + struct drm_virtgpu_resource_create_blob drm_rc_blob = {0}; + struct drm_virtgpu_context_set_param ctx_set_params[3] = {{0}}; + struct CrossDomainInit cmd_init = {{0}}; + + // Initialize the cross domain context. Create one fence context to wait for + // metadata queries. + ctx_set_params[0].param = VIRTGPU_CONTEXT_PARAM_CAPSET_ID; + ctx_set_params[0].value = CAPSET_CROSS_DOMAIN; + ctx_set_params[1].param = VIRTGPU_CONTEXT_PARAM_NUM_RINGS; + ctx_set_params[1].value = 2; + ctx_set_params[2].param = VIRTGPU_CONTEXT_PARAM_POLL_RINGS_MASK; + ctx_set_params[2].value = 1 << CROSS_DOMAIN_CHANNEL_RING; + + init.ctx_set_params = (unsigned long long)&ctx_set_params[0]; + init.num_params = 3; + ret = drmIoctl(virtgpu_, DRM_IOCTL_VIRTGPU_CONTEXT_INIT, &init); + if (ret) { + fprintf(stderr, "DRM_IOCTL_VIRTGPU_CONTEXT_INIT failed with %s\n", + strerror(errno)); + return ret; + } + + // Create a shared ring buffer to read metadata queries. + drm_rc_blob.size = PAGE_SIZE; + drm_rc_blob.blob_mem = VIRTGPU_BLOB_MEM_GUEST; + drm_rc_blob.blob_flags = VIRTGPU_BLOB_FLAG_USE_MAPPABLE; + + ret = + drmIoctl(virtgpu_, DRM_IOCTL_VIRTGPU_RESOURCE_CREATE_BLOB, &drm_rc_blob); + if (ret < 0) { + fprintf(stderr, "DRM_VIRTGPU_RESOURCE_CREATE_BLOB failed with %s\n", + strerror(errno)); + return ret; + } + + ring_handle_ = drm_rc_blob.bo_handle; + + // Map shared ring buffer. + map.handle = ring_handle_; + ret = drmIoctl(virtgpu_, DRM_IOCTL_VIRTGPU_MAP, &map); + if (ret < 0) { + fprintf(stderr, "DRM_IOCTL_VIRTGPU_MAP failed with %s\n", strerror(errno)); + return ret; + } + + ring_addr_ = mmap(0, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, virtgpu_, + map.offset); + + if (ring_addr_ == MAP_FAILED) { + fprintf(stderr, "mmap failed with %s\n", strerror(errno)); + return ret; + } + + // Notify host about ring buffer + cmd_init.hdr.cmd = CROSS_DOMAIN_CMD_INIT; + cmd_init.hdr.cmd_size = sizeof(struct CrossDomainInit); + cmd_init.ring_id = drm_rc_blob.res_handle; + cmd_init.channel_type = CROSS_DOMAIN_CHANNEL_TYPE_WAYLAND; + ret = submit_cmd((uint32_t*)&cmd_init, cmd_init.hdr.cmd_size, + CROSS_DOMAIN_RING_NONE, false); + if (ret < 0) + return ret; + + // Start polling right after initialization + ret = channel_poll(); + if (ret < 0) + return ret; + + out_channel_fd = virtgpu_; + return 0; +} + +int32_t VirtGpuChannel::create_pipe(int& out_pipe_fd) { + // This may be undesirable given your point of view, since the host + // generates the descriptor IDs. However, given the way Sommelier is + // designed and the order of events that occurs, this is safe to do. + // The host will verify this assumption, and we can always change it + // later. But this avoids waiting for the host to create a pipe and + // return the ID. + descriptor_id_ += 2; + + return create_pipe_internal(out_pipe_fd, descriptor_id_, + CROSS_DOMAIN_ID_TYPE_READ_PIPE); +} + +int32_t VirtGpuChannel::send(const struct WaylandSendReceive& send) { + int32_t ret; + uint8_t cmd_buffer[DEFAULT_BUFFER_SIZE]; + + struct CrossDomainSendReceive* cmd_send = + (struct CrossDomainSendReceive*)cmd_buffer; + + void* send_data = &cmd_buffer[sizeof(struct CrossDomainSendReceive)]; + + memset(cmd_send, 0, sizeof(struct CrossDomainSendReceive)); + + if (send.data_size > max_send_size()) + return -EINVAL; + + if (send.num_fds > CROSS_DOMAIN_MAX_IDENTIFIERS) + return -EINVAL; + + cmd_send->hdr.cmd = CROSS_DOMAIN_CMD_SEND; + cmd_send->hdr.cmd_size = + sizeof(struct CrossDomainSendReceive) + send.data_size; + + memcpy(send_data, send.data, send.data_size); + cmd_send->opaque_data_size = send.data_size; + + for (uint32_t i = 0; i < CROSS_DOMAIN_MAX_IDENTIFIERS; i++) { + if (i >= send.num_fds) + break; + + ret = fd_analysis(send.fds[i], cmd_send->identifiers[i], + cmd_send->identifier_types[i]); + if (ret) + return ret; + + cmd_send->num_identifiers++; + } + + ret = submit_cmd((uint32_t*)cmd_send, cmd_send->hdr.cmd_size, + CROSS_DOMAIN_RING_NONE, false); + if (ret < 0) + return ret; + + return 0; +} + +int32_t VirtGpuChannel::handle_channel_event( + enum WaylandChannelEvent& event_type, + struct WaylandSendReceive& receive, + int& out_read_pipe) { + int32_t ret; + struct CrossDomainHeader* cmd_hdr = (struct CrossDomainHeader*)ring_addr_; + ssize_t bytes_read; + struct drm_event dummy_event; + + bytes_read = read(virtgpu_, &dummy_event, sizeof(struct drm_event)); + if (bytes_read < (int)sizeof(struct drm_event)) { + fprintf(stderr, "invalid event size\n"); + return -EINVAL; + } + + if (dummy_event.type != VIRTGPU_EVENT_FENCE_SIGNALED) { + fprintf(stderr, "invalid event type\n"); + return -EINVAL; + } + + if (cmd_hdr->cmd == CROSS_DOMAIN_CMD_RECEIVE) { + event_type = WaylandChannelEvent::Receive; + ret = handle_receive(event_type, receive, out_read_pipe); + if (ret) + return ret; + } else if (cmd_hdr->cmd == CROSS_DOMAIN_CMD_READ) { + event_type = WaylandChannelEvent::Read; + ret = handle_read(); + if (ret) + return ret; + } else { + return -EINVAL; + } + + // Start polling again + ret = channel_poll(); + if (ret < 0) + return ret; + + return 0; +} + +int32_t VirtGpuChannel::allocate( + const struct WaylandBufferCreateInfo& create_info, + struct WaylandBufferCreateOutput& create_output) { + int32_t ret; + uint64_t blob_id; + + ret = image_query(create_info, create_output, blob_id); + if (ret < 0) { + fprintf(stderr, "image query failed\n"); + return ret; + } + + return create_host_blob(blob_id, create_output.host_size, create_output.fd); +} + +int32_t VirtGpuChannel::sync(int dmabuf_fd, uint64_t flags) { + // Unimplemented for now, but just need CROSS_DOMAIN_CMD_SYNC. + return 0; +} + +int32_t VirtGpuChannel::handle_pipe(int read_fd, bool readable, bool& hang_up) { + uint8_t cmd_buffer[DEFAULT_BUFFER_SIZE]; + ssize_t bytes_read; + int ret; + size_t index; + + struct CrossDomainReadWrite* cmd_write = + (struct CrossDomainReadWrite*)cmd_buffer; + + void* write_data = &cmd_buffer[sizeof(struct CrossDomainReadWrite)]; + + memset(cmd_write, 0, sizeof(struct CrossDomainReadWrite)); + + cmd_write->hdr.cmd = CROSS_DOMAIN_CMD_WRITE; + cmd_write->identifier = 0xffffffff; + + ret = pipe_lookup(CROSS_DOMAIN_ID_TYPE_WRITE_PIPE, cmd_write->identifier, + read_fd, index); + if (ret < 0) + return -EINVAL; + + if (readable) { + bytes_read = read(read_fd, write_data, MAX_WRITE_SIZE); + if (bytes_read > 0) { + cmd_write->opaque_data_size = bytes_read; + + if ((size_t)bytes_read < MAX_WRITE_SIZE) + hang_up = true; + else + hang_up = false; + + } else if (bytes_read == 0) { + hang_up = true; + } else { + return -EINVAL; + } + } + + cmd_write->hdr.cmd_size = + sizeof(struct CrossDomainReadWrite) + cmd_write->opaque_data_size; + cmd_write->hang_up = hang_up; + + ret = submit_cmd((uint32_t*)cmd_write, cmd_write->hdr.cmd_size, + CROSS_DOMAIN_RING_NONE, false); + if (ret < 0) + return ret; + + if (hang_up) { + close(read_fd); + std::swap(pipe_cache_[index], pipe_cache_.back()); + pipe_cache_.pop_back(); + } + + return 0; +} + +int32_t VirtGpuChannel::submit_cmd(uint32_t* cmd, + uint32_t size, + uint32_t ring_idx, + bool wait) { + int32_t ret; + struct drm_virtgpu_3d_wait wait_3d = {0}; + struct drm_virtgpu_execbuffer exec = {0}; + + exec.command = (uint64_t)&cmd[0]; + exec.size = size; + if (ring_idx != CROSS_DOMAIN_RING_NONE) { + exec.flags = VIRTGPU_EXECBUF_RING_IDX; + exec.ring_idx = ring_idx; + exec.bo_handles = (uint64_t)&ring_handle_; + exec.num_bo_handles = 1; + } + + ret = drmIoctl(virtgpu_, DRM_IOCTL_VIRTGPU_EXECBUFFER, &exec); + if (ret < 0) { + fprintf(stderr, "DRM_IOCTL_VIRTGPU_EXECBUFFER failed with %s\n", + strerror(errno)); + return -EINVAL; + } + + // This is the most traditional way to wait for virtgpu to be finished. We + // submit a list of handles to the GPU, and wait for the GPU to be done + // processing them. In our case, the handle is the shared ring buffer between + // the guest proxy (Sommelier) and host compositor proxy (cross domain context + // type in crosvm). More sophistication will be needed in the future if the + // virtgpu approach has any hope of success. + if (wait) { + ret = -EAGAIN; + while (ret == -EAGAIN) { + wait_3d.handle = ring_handle_; + ret = drmIoctl(virtgpu_, DRM_IOCTL_VIRTGPU_WAIT, &wait_3d); + } + } + + if (ret < 0) { + fprintf(stderr, "DRM_IOCTL_VIRTGPU_WAIT failed with %s\n", strerror(errno)); + return ret; + } + + return 0; +} + +int32_t VirtGpuChannel::image_query(const struct WaylandBufferCreateInfo& input, + struct WaylandBufferCreateOutput& output, + uint64_t& blob_id) { + int32_t ret = 0; + uint32_t* addr = (uint32_t*)ring_addr_; + struct CrossDomainGetImageRequirements cmd_get_reqs = {{0}}; + struct BufferDescription new_desc = {{0}}; + + // Sommelier is single threaded, so no need for locking. + for (const auto& desc : description_cache_) { + if (desc.input.width == input.width && desc.input.height == input.height && + desc.input.drm_format == input.drm_format) { + memcpy(&output, &desc.output, sizeof(struct WaylandBufferCreateOutput)); + blob_id = (uint64_t)desc.blob_id; + return 0; + } + } + + cmd_get_reqs.hdr.cmd = CROSS_DOMAIN_CMD_GET_IMAGE_REQUIREMENTS; + cmd_get_reqs.hdr.cmd_size = sizeof(struct CrossDomainGetImageRequirements); + + cmd_get_reqs.width = input.width; + cmd_get_reqs.height = input.height; + cmd_get_reqs.drm_format = input.drm_format; + + // Assumes a gbm-like API on the host + cmd_get_reqs.flags = GBM_BO_USE_LINEAR | GBM_BO_USE_SCANOUT; + + ret = submit_cmd((uint32_t*)&cmd_get_reqs, cmd_get_reqs.hdr.cmd_size, + CROSS_DOMAIN_QUERY_RING, true); + if (ret < 0) + return ret; + + new_desc.output.fd = -1; + memcpy(&new_desc.input, &input, sizeof(struct WaylandBufferCreateInfo)); + memcpy(&new_desc.output.strides, &addr[0], 4 * sizeof(uint32_t)); + memcpy(&new_desc.output.offsets, &addr[4], 4 * sizeof(uint32_t)); + memcpy(&new_desc.output.host_size, &addr[10], sizeof(uint64_t)); + memcpy(&new_desc.blob_id, &addr[12], sizeof(uint32_t)); + + // Sanity check + if (!input.dmabuf) { + if (new_desc.output.host_size < input.size) { + fprintf(stderr, "invalid host size\n"); + return -EINVAL; + } + } + + memcpy(&output.strides, &new_desc.output.strides, 4 * sizeof(uint32_t)); + memcpy(&output.offsets, &new_desc.output.offsets, 4 * sizeof(uint32_t)); + output.host_size = new_desc.output.host_size; + blob_id = (uint64_t)new_desc.blob_id; + + description_cache_.push_back(new_desc); + return 0; +} + +int32_t VirtGpuChannel::close_gem_handle(uint32_t gem_handle) { + int32_t ret; + struct drm_gem_close gem_close = {0}; + + gem_close.handle = gem_handle; + ret = drmIoctl(virtgpu_, DRM_IOCTL_GEM_CLOSE, &gem_close); + if (ret) { + fprintf(stderr, "DRM_IOCTL_GEM_CLOSE failed (handle=%x) error %s\n", + gem_handle, strerror(errno)); + return -errno; + } + + return 0; +} + +int32_t VirtGpuChannel::channel_poll(void) { + int32_t ret; + struct CrossDomainPoll cmd_poll = {{0}}; + + cmd_poll.hdr.cmd = CROSS_DOMAIN_CMD_POLL; + cmd_poll.hdr.cmd_size = sizeof(struct CrossDomainPoll); + + ret = submit_cmd((uint32_t*)&cmd_poll, cmd_poll.hdr.cmd_size, + CROSS_DOMAIN_CHANNEL_RING, false); + if (ret < 0) + return ret; + + return 0; +} + +int32_t VirtGpuChannel::create_host_blob(uint64_t blob_id, + uint64_t size, + int& out_fd) { + int32_t ret; + struct drm_virtgpu_resource_create_blob drm_rc_blob = {0}; + + drm_rc_blob.size = size; + drm_rc_blob.blob_mem = VIRTGPU_BLOB_MEM_HOST3D; + drm_rc_blob.blob_flags = + VIRTGPU_BLOB_FLAG_USE_MAPPABLE | VIRTGPU_BLOB_FLAG_USE_SHAREABLE; + drm_rc_blob.blob_id = blob_id; + + ret = + drmIoctl(virtgpu_, DRM_IOCTL_VIRTGPU_RESOURCE_CREATE_BLOB, &drm_rc_blob); + if (ret < 0) { + fprintf(stderr, "DRM_VIRTGPU_RESOURCE_CREATE_BLOB failed with %s\n", + strerror(errno)); + return -errno; + } + + ret = drmPrimeHandleToFD(virtgpu_, drm_rc_blob.bo_handle, + DRM_CLOEXEC | DRM_RDWR, &out_fd); + if (ret < 0) { + fprintf(stderr, "drmPrimeHandleToFD failed with with %s\n", + strerror(errno)); + return -errno; + } + + // dma-buf owns the reference to underlying memory now. + ret = close_gem_handle(drm_rc_blob.bo_handle); + if (ret < 0) + return ret; + + return 0; +} + +int32_t VirtGpuChannel::create_fd(uint32_t identifier, + uint32_t identifier_type, + uint32_t identifier_size, + int& out_fd) { + // Update descriptor ID based on latest host information. + descriptor_id_ = identifier; + + if (identifier_type == CROSS_DOMAIN_ID_TYPE_VIRTGPU_BLOB) { + return create_host_blob((uint64_t)identifier, (uint64_t)identifier_size, + out_fd); + } else if (identifier_type == CROSS_DOMAIN_ID_TYPE_WRITE_PIPE) { + return create_pipe_internal(out_fd, identifier, identifier_type); + } else { + return -EINVAL; + } +} + +int32_t VirtGpuChannel::fd_analysis(int fd, + uint32_t& identifier, + uint32_t& identifier_type) { + int32_t ret = 0; + uint32_t gem_handle; + ret = drmPrimeFDToHandle(virtgpu_, fd, &gem_handle); + if (ret == 0) { + struct drm_virtgpu_resource_info drm_res_info = {0}; + drm_res_info.bo_handle = gem_handle; + + ret = drmIoctl(virtgpu_, DRM_IOCTL_VIRTGPU_RESOURCE_INFO, &drm_res_info); + + if (ret) { + fprintf(stderr, "resource info failed\n"); + return ret; + } + + identifier = drm_res_info.res_handle; + identifier_type = CROSS_DOMAIN_ID_TYPE_VIRTGPU_BLOB; + } else { + // If it's not a blob, the only other option is a pipe. Check to confirm. + uint32_t inode; + ret = fstat_pipe(fd, inode); + if (ret) + return ret; + + for (const auto& pipe_desc : pipe_cache_) { + if (pipe_desc.inode == inode) { + identifier = pipe_desc.identifier; + identifier_type = pipe_desc.identifier_type; + return 0; + } + } + + return -EINVAL; + } + + return 0; +} + +int32_t VirtGpuChannel::create_pipe_internal(int& out_pipe_fd, + uint32_t identifier, + uint32_t identifier_type) { + int32_t ret; + int fds[2]; + struct PipeDescription pipe_desc = {0}; + bool return_read_pipe = false; + + // When proxying a Wayland pipe, we return one end to the WaylandChannel + // consumer and keep one end to ourselves. Keeping both ends isn't useful. + if (identifier_type == CROSS_DOMAIN_ID_TYPE_READ_PIPE) + return_read_pipe = true; + else if (identifier_type == CROSS_DOMAIN_ID_TYPE_WRITE_PIPE) + return_read_pipe = false; + else + return -EINVAL; + + ret = pipe(fds); + if (ret < 0) { + fprintf(stderr, "pipe creation failed with %s\n", strerror(errno)); + return -errno; + } + + // The same inode number is used for the read/write ends of the pipe. + ret = fstat_pipe(fds[0], pipe_desc.inode); + if (ret < 0) + return ret; + + pipe_desc.read_fd = fds[0]; + pipe_desc.write_fd = fds[1]; + pipe_desc.identifier = identifier; + pipe_desc.identifier_type = identifier_type; + pipe_cache_.push_back(pipe_desc); + + if (return_read_pipe) + out_pipe_fd = fds[0]; + else + out_pipe_fd = fds[1]; + + return 0; +} + +int32_t VirtGpuChannel::handle_receive(enum WaylandChannelEvent& event_type, + struct WaylandSendReceive& receive, + int& out_read_pipe) { + int ret; + struct CrossDomainSendReceive* cmd_receive = + (struct CrossDomainSendReceive*)ring_addr_; + + uint8_t* recv_data = + (uint8_t*)ring_addr_ + sizeof(struct CrossDomainSendReceive); + + for (uint32_t i = 0; i < CROSS_DOMAIN_MAX_IDENTIFIERS; i++) { + if (i < cmd_receive->num_identifiers) { + ret = create_fd(cmd_receive->identifiers[i], + cmd_receive->identifier_types[i], + cmd_receive->identifier_sizes[i], receive.fds[i]); + if (ret) + return ret; + + receive.num_fds++; + + if (cmd_receive->identifier_types[i] == CROSS_DOMAIN_ID_TYPE_WRITE_PIPE) { + size_t index; + int ret = 0; + if (out_read_pipe >= 0) + return -EINVAL; + + ret = pipe_lookup(cmd_receive->identifier_types[i], + cmd_receive->identifiers[i], out_read_pipe, index); + if (ret < 0) + return -EINVAL; + + event_type = WaylandChannelEvent::ReceiveAndProxy; + } + } else { + break; + } + } + + if (cmd_receive->opaque_data_size > 0) { + receive.data = + reinterpret_cast(calloc(1, cmd_receive->opaque_data_size)); + if (!receive.data) + return -ENOMEM; + + memcpy(receive.data, recv_data, cmd_receive->opaque_data_size); + } + + receive.data_size = cmd_receive->opaque_data_size; + return 0; +} + +int32_t VirtGpuChannel::handle_read() { + int write_fd = -1; + int ret = 0; + ssize_t bytes_written; + size_t index; + struct CrossDomainReadWrite* cmd_read = + (struct CrossDomainReadWrite*)ring_addr_; + + uint8_t* read_data = + (uint8_t*)ring_addr_ + sizeof(struct CrossDomainReadWrite); + + ret = pipe_lookup(CROSS_DOMAIN_ID_TYPE_READ_PIPE, cmd_read->identifier, + write_fd, index); + if (ret < 0) + return -EINVAL; + + bytes_written = write(write_fd, read_data, cmd_read->opaque_data_size); + + if (bytes_written < (ssize_t)cmd_read->opaque_data_size) { + fprintf(stderr, "failed to write all necessary bytes\n"); + return -EINVAL; + } + + if (cmd_read->hang_up) { + close(write_fd); + std::swap(pipe_cache_[index], pipe_cache_.back()); + pipe_cache_.pop_back(); + } + + return 0; +} + +int32_t VirtGpuChannel::pipe_lookup(uint32_t identifier_type, + uint32_t& identifier, + int& fd, + size_t& index) { + index = 0; + for (const auto& pipe_desc : pipe_cache_) { + if (pipe_desc.identifier == identifier) { + // The host and guest are proxying the read operation, need to write to + // internally owned file descriptor. + if (identifier_type == CROSS_DOMAIN_ID_TYPE_READ_PIPE) { + fd = pipe_desc.write_fd; + return 0; + } + + // The host and guest are proxying the write operation, need to read from + // internally owned file descriptor. + if (identifier_type == CROSS_DOMAIN_ID_TYPE_WRITE_PIPE) { + fd = pipe_desc.read_fd; + return 0; + } + } + + if (fd == pipe_desc.read_fd || fd == pipe_desc.write_fd) { + identifier = pipe_desc.identifier; + return 0; + } + + index++; + } + + return -EINVAL; +} + +size_t VirtGpuChannel::max_send_size(void) { + return MAX_SEND_SIZE; +} diff --git a/sommelier/virtualization/virtgpu_cross_domain_protocol.h b/sommelier/virtualization/virtgpu_cross_domain_protocol.h new file mode 100644 index 0000000..34fb827 --- /dev/null +++ b/sommelier/virtualization/virtgpu_cross_domain_protocol.h @@ -0,0 +1,111 @@ +// Copyright 2021 The ChromiumOS Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef VM_TOOLS_SOMMELIER_VIRTUALIZATION_VIRTGPU_CROSS_DOMAIN_PROTOCOL_H_ +#define VM_TOOLS_SOMMELIER_VIRTUALIZATION_VIRTGPU_CROSS_DOMAIN_PROTOCOL_H_ + +#include + +// Cross-domain commands (only a maximum of 255 supported) +#define CROSS_DOMAIN_CMD_INIT 1 +#define CROSS_DOMAIN_CMD_GET_IMAGE_REQUIREMENTS 2 +#define CROSS_DOMAIN_CMD_POLL 3 +#define CROSS_DOMAIN_CMD_SEND 4 +#define CROSS_DOMAIN_CMD_RECEIVE 5 +#define CROSS_DOMAIN_CMD_READ 6 +#define CROSS_DOMAIN_CMD_WRITE 7 + +// Channel types (must match rutabaga channel types) +#define CROSS_DOMAIN_CHANNEL_TYPE_WAYLAND 0x0001 +#define CROSS_DOMAIN_CHANNEL_TYPE_CAMERA 0x0002 + +// The maximum number of identifiers (value based on wp_linux_dmabuf) +#define CROSS_DOMAIN_MAX_IDENTIFIERS 4 + +// virtgpu memory resource ID. Also works with non-blob memory resources, +// despite the name. +#define CROSS_DOMAIN_ID_TYPE_VIRTGPU_BLOB 1 + +// virtgpu synchronization resource id. +#define CROSS_DOMAIN_ID_TYPE_VIRTGPU_SYNC 2 + +// ID for Wayland pipe used for reading. The reading is done by the guest proxy +// and the host proxy. The host sends the write end of the proxied pipe over +// the host Wayland socket. +#define CROSS_DOMAIN_ID_TYPE_READ_PIPE 3 + +// ID for Wayland pipe used for writing. The writing is done by the guest and +// the host proxy. The host receives the write end of the pipe over the host +// Wayland socket. +#define CROSS_DOMAIN_ID_TYPE_WRITE_PIPE 4 + +// No ring used +#define CROSS_DOMAIN_RING_NONE 0xffffffff +// A ring for metadata queries. +#define CROSS_DOMAIN_QUERY_RING 0 +// A ring based on this particular context's channel. +#define CROSS_DOMAIN_CHANNEL_RING 1 + +struct CrossDomainCapabilities { + uint32_t version; + uint32_t supported_channels; + uint32_t supports_dmabuf; + uint32_t supports_external_gpu_memory; +}; + +struct CrossDomainImageRequirements { + uint32_t strides[4]; + uint32_t offsets[4]; + uint64_t modifier; + uint64_t size; + uint32_t blob_id; + uint32_t map_info; + int32_t memory_idx; + int32_t physical_device_idx; +}; + +struct CrossDomainHeader { + uint8_t cmd; + uint8_t fence_ctx_idx; + uint16_t cmd_size; + uint32_t pad; +}; + +struct CrossDomainInit { + struct CrossDomainHeader hdr; + uint32_t ring_id; + uint32_t channel_type; +}; + +struct CrossDomainGetImageRequirements { + struct CrossDomainHeader hdr; + uint32_t width; + uint32_t height; + uint32_t drm_format; + uint32_t flags; +}; + +struct CrossDomainPoll { + struct CrossDomainHeader hdr; + uint64_t pad; +}; + +struct CrossDomainSendReceive { + struct CrossDomainHeader hdr; + uint32_t num_identifiers; + uint32_t opaque_data_size; + uint32_t identifiers[CROSS_DOMAIN_MAX_IDENTIFIERS]; + uint32_t identifier_types[CROSS_DOMAIN_MAX_IDENTIFIERS]; + uint32_t identifier_sizes[CROSS_DOMAIN_MAX_IDENTIFIERS]; +}; + +struct CrossDomainReadWrite { + struct CrossDomainHeader hdr; + uint32_t identifier; + uint32_t hang_up; + uint32_t opaque_data_size; + uint32_t pad; +}; + +#endif diff --git a/sommelier/virtualization/virtwl_channel.cc b/sommelier/virtualization/virtwl_channel.cc new file mode 100644 index 0000000..8e52b62 --- /dev/null +++ b/sommelier/virtualization/virtwl_channel.cc @@ -0,0 +1,225 @@ +// Copyright 2020 The ChromiumOS Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "linux-headers/virtwl.h" // NOLINT(build/include_directory) +#include "wayland_channel.h" // NOLINT(build/include_directory) + +#define VIRTWL_DEVICE "/dev/wl0" +#define MAX_SEND_SIZE (DEFAULT_BUFFER_SIZE - sizeof(struct virtwl_ioctl_txn)) + +VirtWaylandChannel::~VirtWaylandChannel() { + if (virtwl_ >= 0) + close(virtwl_); +} + +int32_t VirtWaylandChannel::init() { + virtwl_ = open(VIRTWL_DEVICE, O_RDWR); + int32_t ret; + struct WaylandBufferCreateInfo create_info = {0}; + struct WaylandBufferCreateOutput create_output = {0}; + create_output.fd = -1; + + if (virtwl_ == -1) + return -errno; + + create_info.dmabuf = true; + supports_dmabuf_ = true; + + ret = allocate(create_info, create_output); + if (ret && errno == ENOTTY) { + fprintf(stderr, + "warning: virtwl-dmabuf driver not supported by host," + " using virtwl instead\n"); + supports_dmabuf_ = false; + } else if (create_output.fd >= 0) { + // Close the returned dmabuf fd in case the invalid dmabuf metadata + // given above actually manages to return an fd successfully. + close(create_output.fd); + create_output.fd = -1; + } + + return 0; +} + +bool VirtWaylandChannel::supports_dmabuf(void) { + return supports_dmabuf_; +} + +int32_t VirtWaylandChannel::create_context(int& out_channel_fd) { + int ret; + struct virtwl_ioctl_new new_ctx = { + .type = VIRTWL_IOCTL_NEW_CTX, + .fd = -1, + .flags = 0, + }; + + ret = ioctl(virtwl_, VIRTWL_IOCTL_NEW, &new_ctx); + if (ret) + return -errno; + + out_channel_fd = new_ctx.fd; + + return 0; +} + +int32_t VirtWaylandChannel::create_pipe(int& out_pipe_fd) { + int ret; + struct virtwl_ioctl_new new_pipe = { + .type = VIRTWL_IOCTL_NEW_PIPE_READ, + .fd = -1, + .flags = 0, + }; + + new_pipe.size = 0; + + ret = ioctl(virtwl_, VIRTWL_IOCTL_NEW, &new_pipe); + if (ret) + return -errno; + + out_pipe_fd = new_pipe.fd; + + return 0; +} + +int32_t VirtWaylandChannel::send(const struct WaylandSendReceive& send) { + int ret; + uint8_t ioctl_buffer[DEFAULT_BUFFER_SIZE]; + + struct virtwl_ioctl_txn* txn = (struct virtwl_ioctl_txn*)ioctl_buffer; + void* send_data = &txn->data; + + if (send.data_size > max_send_size()) + return -EINVAL; + + memcpy(send_data, send.data, send.data_size); + + for (uint32_t i = 0; i < WAYLAND_MAX_FDs; i++) { + if (i < send.num_fds) { + txn->fds[i] = send.fds[i]; + } else { + txn->fds[i] = -1; + } + } + + txn->len = send.data_size; + ret = ioctl(send.channel_fd, VIRTWL_IOCTL_SEND, txn); + if (ret) + return -errno; + + return 0; +} + +int32_t VirtWaylandChannel::handle_channel_event( + enum WaylandChannelEvent& event_type, + struct WaylandSendReceive& receive, + int& out_read_pipe) { + int ret; + uint8_t ioctl_buffer[DEFAULT_BUFFER_SIZE]; + + struct virtwl_ioctl_txn* txn = (struct virtwl_ioctl_txn*)ioctl_buffer; + size_t max_recv_size = sizeof(ioctl_buffer) - sizeof(struct virtwl_ioctl_txn); + void* recv_data = &txn->data; + + txn->len = max_recv_size; + ret = ioctl(receive.channel_fd, VIRTWL_IOCTL_RECV, txn); + if (ret) + return -errno; + + for (uint32_t i = 0; i < WAYLAND_MAX_FDs; i++) { + if (txn->fds[i] >= 0) { + receive.num_fds++; + receive.fds[i] = txn->fds[i]; + } else { + break; + } + } + + if (txn->len > 0) { + receive.data = reinterpret_cast(calloc(1, txn->len)); + if (!receive.data) + return -ENOMEM; + + memcpy(receive.data, recv_data, txn->len); + } + + receive.data_size = txn->len; + event_type = WaylandChannelEvent::Receive; + return 0; +} + +int32_t VirtWaylandChannel::allocate( + const struct WaylandBufferCreateInfo& create_info, + struct WaylandBufferCreateOutput& create_output) { + int ret; + struct virtwl_ioctl_new ioctl_new = {0}; + + if (create_info.dmabuf) { + ioctl_new.type = VIRTWL_IOCTL_NEW_DMABUF; + ioctl_new.fd = -1; + ioctl_new.flags = 0; + ioctl_new.dmabuf.width = create_info.width; + ioctl_new.dmabuf.height = create_info.height; + ioctl_new.dmabuf.format = create_info.drm_format; + } else { + ioctl_new.type = VIRTWL_IOCTL_NEW_ALLOC; + ioctl_new.fd = -1; + ioctl_new.flags = 0; + ioctl_new.size = create_info.size; + create_output.host_size = create_info.size; + } + + ret = ioctl(virtwl_, VIRTWL_IOCTL_NEW, &ioctl_new); + if (ret) + return -errno; + + if (create_info.dmabuf) { + create_output.strides[0] = ioctl_new.dmabuf.stride0; + create_output.strides[1] = ioctl_new.dmabuf.stride1; + create_output.strides[2] = ioctl_new.dmabuf.stride2; + + create_output.offsets[0] = ioctl_new.dmabuf.offset0; + create_output.offsets[1] = ioctl_new.dmabuf.offset1; + create_output.offsets[2] = ioctl_new.dmabuf.offset2; + + // The common layer will consider multi-planar sizes as needed. + create_output.host_size = create_output.strides[0] * create_info.height; + } + + create_output.fd = ioctl_new.fd; + + return 0; +} + +int32_t VirtWaylandChannel::sync(int dmabuf_fd, uint64_t flags) { + struct virtwl_ioctl_dmabuf_sync sync = {0}; + int ret; + + sync.flags = flags; + ret = ioctl(dmabuf_fd, VIRTWL_IOCTL_DMABUF_SYNC, &sync); + if (ret) + return -errno; + + return 0; +} + +int32_t VirtWaylandChannel::handle_pipe(int read_fd, + bool readable, + bool& hang_up) { + return 0; +} + +size_t VirtWaylandChannel::max_send_size(void) { + return MAX_SEND_SIZE; +} diff --git a/sommelier/virtualization/wayland_channel.h b/sommelier/virtualization/wayland_channel.h new file mode 100644 index 0000000..d6df1cf --- /dev/null +++ b/sommelier/virtualization/wayland_channel.h @@ -0,0 +1,288 @@ +// Copyright 2020 The ChromiumOS Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +#ifndef VM_TOOLS_SOMMELIER_VIRTUALIZATION_WAYLAND_CHANNEL_H_ +#define VM_TOOLS_SOMMELIER_VIRTUALIZATION_WAYLAND_CHANNEL_H_ + +#include +#include +#include + +/* + * Copied from `VIRTWL_SEND_MAX_ALLOCS`. It was originally set this way + * because it seemed like a reasonable limit. + */ +#define WAYLAND_MAX_FDs 28 + +// Default buffer size based on the size of a typical page. +#define DEFAULT_BUFFER_SIZE 4096 + +struct WaylandSendReceive { + int channel_fd; + + int fds[WAYLAND_MAX_FDs]; + uint32_t num_fds; + uint8_t* data; + size_t data_size; +}; + +enum WaylandChannelEvent { + None, + Receive, + ReceiveAndProxy, + Read, +}; + +struct WaylandBufferCreateInfo { + /* + * If true, create a dmabuf on the host. If not, create a shared memory + * region. A dmabuf can be scanned out by the display engine directly, + * enabling zero copy. A shared memory region necessitates a copy to a + * dma-buf by the host compositor. + */ + bool dmabuf; + + /* + * dma-buf parameters. The allocation is done by host minigbm and used when + * crosvm is built with the "wl-dmabuf" feature and virtgpu 3d is not + * enabled. The modifier is not present, because we only want to allocate + * linear zero-copy buffers in this case. The modifier makes sense when + * virtgpu 3d is enabled, but in that case guest Mesa gbm (backed by Virgl) + * allocates the resource, not sommelier. + */ + uint32_t width; + uint32_t height; + uint32_t drm_format; + + /* + * Shared memory region parameters. The allocation is done by memfd(..) on + * the host. + */ + uint32_t size; +}; + +/* + * Linux mode-setting APIs [drmModeAddFB2(..)] and Wayland normally specify + * four planes, even though three are used in practice. Follow that convention + * here. + */ +struct WaylandBufferCreateOutput { + int fd; + uint32_t offsets[4]; + uint32_t strides[4]; + uint64_t host_size; +}; + +class WaylandChannel { + public: + WaylandChannel() {} + virtual ~WaylandChannel() {} + + // Initializes the Wayland Channel. Returns 0 on success, -errno on failure. + virtual int32_t init() = 0; + + // Returns true if the Wayland channel supports dmabuf, false otherwise. If + // dmabuf is supported, Sommelier will use the `zwp_linux_dmabuf_v1` + // protocol. + virtual bool supports_dmabuf(void) = 0; + + // Creates a new context for handling the wayland command stream. Returns 0 + // on success, and a pollable `out_channel_fd`. This fd represents the + // connection to the host compositor, and used for subsequent `send` and + // `receive` operations. + // + // Returns -errno on failure. + virtual int32_t create_context(int& out_channel_fd) = 0; + + // Creates a new clipboard pipe for Wayland input. Note this interface can't + // wrap a call to "pipe", and is named based on VIRTWL_IOCTL_NEW_PIPE. A new + // interface may be designed in the future. + // + // Returns 0 on success, and a readable `out_pipe_fd`. + // Returns -errno on failure. + virtual int32_t create_pipe(int& out_pipe_fd) = 0; + + // Sends fds and associated commands to the host [like sendmsg(..)]. The fds + // are converted to host handles using an implementation specific method. + // For virtwl, either: + // (a) virtwl allocated resources are sent. + // (b) The virtgpu resource handle is fished out by virtwl. + // + // Returns 0 on success. Returns -errno on failure. If `send.data_size` is + // than greater zero, then the caller must provide a pointer to valid memory + // in `send.data`. + virtual int32_t send(const struct WaylandSendReceive& send) = 0; + + // Handles a poll event on the channel file descriptor. + // + // Returns 0 on success. Returns -errno on failure. On success, the type of + // event is given by `event_type`. + // + // If `event_type` is WaylandChannelEvent::Receive, the caller must forward + // received fds and associated commands to the client. + // + // If `event_type` is WaylandChannelEvent::ReceiveAndProxy, `out_read_pipe` + // is also returned in addition to the `receive` data. The caller does not + // take ownership of `out_read_pipe`. The caller must poll `out_read_pipe` + // in addition to forwarding the data given by `receive`. The `handle_pipe` + // function must be called the case of `out_read_pipe` event. + // + // In both above cases, if the returned `receive.data_size` is than greater + // zero, then the caller takes ownership of `receive.data` and must free(..) + // the memory when appropriate. + // + // If `event_type` is WaylandChannelEvent::Read, then both `out_read_pipe` and + // `receive` are meaningless. The implementation handles the event internally. + virtual int32_t handle_channel_event(enum WaylandChannelEvent& event_type, + struct WaylandSendReceive& receive, + int& out_read_pipe) = 0; + + // Allocates a shared memory resource or dma-buf on the host. Maps it into + // the guest. The intended use case for this function is sharing resources + // with the host compositor when virtgpu 3d is not enabled. + // + // Returns 0 on success. Returns -errno on success. + virtual int32_t allocate(const struct WaylandBufferCreateInfo& create_info, + struct WaylandBufferCreateOutput& create_output) = 0; + + // Synchronizes accesses to previously created host dma-buf. + // Returns 0 on success. Returns -errno on failure. + virtual int32_t sync(int dmabuf_fd, uint64_t flags) = 0; + + // Reads from the specified `read_fd` and forwards to the host if `readable` + // is true. Closes the `read_fd` and the proxied write fd on the host if + // `hang_up` is true and all the data has been read. + // + // `read_fd` *must* be a read pipe given by `handle_channel_event` when the + // `event_type` is WaylandChannelEvent::ReceiveAndProxy. + virtual int32_t handle_pipe(int read_fd, bool readable, bool& hang_up) = 0; + + // Returns the maximum size of opaque data that the channel is able to handle + // in the `send` function. Must be less than or equal to DEFAULT_BUFFER_SIZE. + virtual size_t max_send_size(void) = 0; +}; + +class VirtWaylandChannel : public WaylandChannel { + public: + VirtWaylandChannel() : virtwl_{-1}, supports_dmabuf_(false) {} + ~VirtWaylandChannel(); + + int32_t init() override; + bool supports_dmabuf() override; + int32_t create_context(int& out_channel_fd) override; + int32_t create_pipe(int& out_pipe_fd) override; + int32_t send(const struct WaylandSendReceive& send) override; + int32_t handle_channel_event(enum WaylandChannelEvent& event_type, + struct WaylandSendReceive& receive, + int& out_read_pipe) override; + + int32_t allocate(const struct WaylandBufferCreateInfo& create_info, + struct WaylandBufferCreateOutput& create_output) override; + + int32_t sync(int dmabuf_fd, uint64_t flags) override; + int32_t handle_pipe(int read_fd, bool readable, bool& hang_up) override; + size_t max_send_size(void) override; + + private: + // virtwl device file descriptor + int32_t virtwl_; + bool supports_dmabuf_; +}; + +class VirtGpuChannel : public WaylandChannel { + public: + VirtGpuChannel() + : virtgpu_{-1}, + ring_addr_{MAP_FAILED}, + ring_handle_{0}, + supports_dmabuf_(false), + descriptor_id_{1} {} + ~VirtGpuChannel(); + + int32_t init() override; + bool supports_dmabuf() override; + int32_t create_context(int& out_channel_fd) override; + int32_t create_pipe(int& out_pipe_fd) override; + int32_t send(const struct WaylandSendReceive& send) override; + int32_t handle_channel_event(enum WaylandChannelEvent& event_type, + struct WaylandSendReceive& receive, + int& out_read_pipe) override; + + int32_t allocate(const struct WaylandBufferCreateInfo& create_info, + struct WaylandBufferCreateOutput& create_output) override; + + int32_t sync(int dmabuf_fd, uint64_t flags) override; + int32_t handle_pipe(int read_fd, bool readable, bool& hang_up) override; + size_t max_send_size(void) override; + + private: + /* + * This provides the full description of the buffer -- width, height, strides, + * offsets and host_size. Meant for internal virtgpu channel use only. + */ + struct BufferDescription { + struct WaylandBufferCreateInfo input; + struct WaylandBufferCreateOutput output; + uint32_t blob_id; + }; + + /* + * Provides the read end and write end of a pipe, along with the inode (a + * guest unique identifier) and host descriptor id; + */ + struct PipeDescription { + int read_fd; + int write_fd; + uint32_t identifier_type; + uint32_t inode; + uint32_t identifier; + }; + + int32_t image_query(const struct WaylandBufferCreateInfo& input, + struct WaylandBufferCreateOutput& output, + uint64_t& blob_id); + + int32_t submit_cmd(uint32_t* cmd, + uint32_t size, + uint32_t ring_idx, + bool wait); + int32_t channel_poll(void); + int32_t close_gem_handle(uint32_t gem_handle); + int32_t create_host_blob(uint64_t blob_id, uint64_t size, int& out_fd); + + int32_t fd_analysis(int fd, uint32_t& identifier, uint32_t& identifier_type); + + int32_t create_fd(uint32_t identifier, + uint32_t identifier_type, + uint32_t identifier_size, + int& out_fd); + + int32_t create_pipe_internal(int& out_pipe_fd, + uint32_t identifier, + uint32_t identifier_type); + + int32_t handle_receive(enum WaylandChannelEvent& event_type, + struct WaylandSendReceive& receive, + int& out_read_pipe); + + int32_t handle_read(void); + + int32_t pipe_lookup(uint32_t identifier_type, + uint32_t& identifier, + int& fd, + size_t& index); + + int32_t virtgpu_; + void* ring_addr_; + uint32_t ring_handle_; + bool supports_dmabuf_; + // Matches the crosvm-side descriptor_id, must be an odd number. + uint32_t descriptor_id_; + + std::vector description_cache_; + std::vector pipe_cache_; +}; + +int open_virtgpu(char** drm_device); + +#endif // VM_TOOLS_SOMMELIER_VIRTUALIZATION_WAYLAND_CHANNEL_H_ diff --git a/sommelier/wayland-protocol.gypi b/sommelier/wayland-protocol.gypi deleted file mode 100644 index 18b6d4c..0000000 --- a/sommelier/wayland-protocol.gypi +++ /dev/null @@ -1,34 +0,0 @@ -# Caution!: GYP to GN migration is happening. If you update this file, please -# update vm_tools/sommelier/wayland-protocol.gni too accordingly. -{ - 'variables': { - 'wayland_dir': '<(SHARED_INTERMEDIATE_DIR)/<(wayland_out_dir)', - 'wayland_in_dir%': '.', - }, - 'rules': [ - { - 'rule_name': 'genwayland', - 'extension': 'xml', - 'outputs': [ - '<(wayland_dir)/<(RULE_INPUT_ROOT)-protocol.c', - '<(wayland_dir)/<(RULE_INPUT_ROOT)-client-protocol.h', - '<(wayland_dir)/<(RULE_INPUT_ROOT)-server-protocol.h', - ], - 'action': [ - 'sh', - '-c', - 'wayland-scanner code < <(wayland_in_dir)/<(RULE_INPUT_NAME) > <(wayland_dir)/<(RULE_INPUT_ROOT)-protocol.c; wayland-scanner client-header < <(wayland_in_dir)/<(RULE_INPUT_NAME) > <(wayland_dir)/<(RULE_INPUT_ROOT)-client-protocol.h; wayland-scanner server-header < <(wayland_in_dir)/<(RULE_INPUT_NAME) > <(wayland_dir)/<(RULE_INPUT_ROOT)-server-protocol.h', - ], - 'message': 'Generating Wayland C code from <(RULE_INPUT_PATH)', - 'process_outputs_as_sources': 1, - }, - ], - # This target exports a hard dependency because it generates header - # files. - 'hard_dependency': 1, - 'direct_dependent_settings': { - 'include_dirs': [ - '<(wayland_dir)', - ], - }, -} diff --git a/sommelier/wayland_protocol.gni b/sommelier/wayland_protocol.gni index 4f18a0c..7a82c63 100644 --- a/sommelier/wayland_protocol.gni +++ b/sommelier/wayland_protocol.gni @@ -1,4 +1,4 @@ -# Copyright 2019 The Chromium OS Authors. All rights reserved. +# Copyright 2019 The ChromiumOS Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -37,9 +37,7 @@ template("wayland_protocol_library") { sources = invoker.sources script = "//common-mk/file_generator_wrapper.py" output_file = "${wayland_dir}/{{source_name_part}}${g.output_suffix}" - outputs = [ - output_file, - ] + outputs = [ output_file ] args = [ "wayland-scanner", g.subcommand, diff --git a/sommelier/weak-resource-ptr.h b/sommelier/weak-resource-ptr.h new file mode 100644 index 0000000..77ca971 --- /dev/null +++ b/sommelier/weak-resource-ptr.h @@ -0,0 +1,65 @@ +// Copyright 2022 The ChromiumOS Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef VM_TOOLS_SOMMELIER_WEAK_RESOURCE_PTR_H_ +#define VM_TOOLS_SOMMELIER_WEAK_RESOURCE_PTR_H_ + +#include "sommelier.h" // NOLINT(build/include_directory) + +#include +#include +#include +#include +#include + +// WeakResourcePtr is a weak pointer for a proxy object (sl_host_foo). It can +// be useful for objects with events like enter/leave, to keep track of an +// object provided in the enter event and ensure that it is not used if the +// client destroys it. +template +class WeakResourcePtr { + public: + WeakResourcePtr() { + wl_list_init(&destroy_listener_.link); + destroy_listener_.notify = ResourceDestroyed; + } + + ~WeakResourcePtr() { wl_list_remove(&destroy_listener_.link); } + + WeakResourcePtr& operator=(SlHostType* host) { + if (host == host_) + return *this; + + Reset(); + if (host) { + host_ = host; + wl_resource_add_destroy_listener(host_->resource, &destroy_listener_); + } + return *this; + } + + operator bool() const { return host_; } + SlHostType* operator->() const { return host_; } + SlHostType* get() const { return host_; } + + void Reset() { + host_ = nullptr; + // Remove the listener + wl_list_remove(&destroy_listener_.link); + wl_list_init(&destroy_listener_.link); + } + + private: + SlHostType* host_ = nullptr; + // This is always in an initialized state + wl_listener destroy_listener_; + + static void ResourceDestroyed(wl_listener* listener, void* data) { + WeakResourcePtr* ptr; + ptr = wl_container_of(listener, ptr, destroy_listener_); + ptr->Reset(); + } +}; + +#endif // VM_TOOLS_SOMMELIER_WEAK_RESOURCE_PTR_H_