From 1dcad9f1f43d92c75eb54b114fad40ec613c7596 Mon Sep 17 00:00:00 2001 From: Bruce Leidl Date: Fri, 20 Sep 2019 18:57:28 -0400 Subject: [PATCH] add sommelier --- sommelier/BUILD.gn | 145 + sommelier/Makefile | 129 + sommelier/OWNERS | 5 + sommelier/README.md | 247 + sommelier/build/args.gn | 8 + sommelier/config.h | 16 + sommelier/demos/wayland_demo.cc | 319 ++ sommelier/demos/x11_demo.cc | 90 + sommelier/linux/virtio_wl.h | 135 + sommelier/linux/virtwl.h | 64 + sommelier/protocol/aura-shell.xml | 238 + sommelier/protocol/drm.xml | 182 + sommelier/protocol/gtk-shell.xml | 61 + .../keyboard-extension-unstable-v1.xml | 82 + .../protocol/linux-dmabuf-unstable-v1.xml | 348 ++ .../protocol/relative-pointer-unstable-v1.xml | 136 + sommelier/protocol/text-input-unstable-v1.xml | 385 ++ sommelier/protocol/viewporter.xml | 186 + sommelier/protocol/xdg-shell-unstable-v6.xml | 1044 +++++ sommelier/sommelier-compositor.c | 894 ++++ sommelier/sommelier-data-device-manager.c | 580 +++ sommelier/sommelier-display.c | 126 + sommelier/sommelier-drm.c | 259 + sommelier/sommelier-gtk-shell.c | 162 + sommelier/sommelier-output.c | 377 ++ .../sommelier-relative-pointer-manager.c | 146 + sommelier/sommelier-seat.c | 778 ++++ sommelier/sommelier-shell.c | 234 + sommelier/sommelier-shm.c | 328 ++ sommelier/sommelier-subcompositor.c | 153 + sommelier/sommelier-text-input.c | 332 ++ sommelier/sommelier-viewporter.c | 128 + sommelier/sommelier-xdg-shell.c | 563 +++ sommelier/sommelier.c | 4146 +++++++++++++++++ sommelier/sommelier.h | 547 +++ sommelier/wayland-protocol.gypi | 34 + sommelier/wayland_protocol.gni | 65 + 37 files changed, 13672 insertions(+) create mode 100644 sommelier/BUILD.gn create mode 100644 sommelier/Makefile create mode 100644 sommelier/OWNERS create mode 100644 sommelier/README.md create mode 100644 sommelier/build/args.gn create mode 100644 sommelier/config.h create mode 100644 sommelier/demos/wayland_demo.cc create mode 100644 sommelier/demos/x11_demo.cc create mode 100644 sommelier/linux/virtio_wl.h create mode 100644 sommelier/linux/virtwl.h create mode 100644 sommelier/protocol/aura-shell.xml create mode 100644 sommelier/protocol/drm.xml create mode 100644 sommelier/protocol/gtk-shell.xml create mode 100644 sommelier/protocol/keyboard-extension-unstable-v1.xml create mode 100644 sommelier/protocol/linux-dmabuf-unstable-v1.xml create mode 100644 sommelier/protocol/relative-pointer-unstable-v1.xml create mode 100644 sommelier/protocol/text-input-unstable-v1.xml create mode 100644 sommelier/protocol/viewporter.xml create mode 100644 sommelier/protocol/xdg-shell-unstable-v6.xml create mode 100644 sommelier/sommelier-compositor.c create mode 100644 sommelier/sommelier-data-device-manager.c create mode 100644 sommelier/sommelier-display.c create mode 100644 sommelier/sommelier-drm.c create mode 100644 sommelier/sommelier-gtk-shell.c create mode 100644 sommelier/sommelier-output.c create mode 100644 sommelier/sommelier-relative-pointer-manager.c create mode 100644 sommelier/sommelier-seat.c create mode 100644 sommelier/sommelier-shell.c create mode 100644 sommelier/sommelier-shm.c create mode 100644 sommelier/sommelier-subcompositor.c create mode 100644 sommelier/sommelier-text-input.c create mode 100644 sommelier/sommelier-viewporter.c create mode 100644 sommelier/sommelier-xdg-shell.c create mode 100644 sommelier/sommelier.c create mode 100644 sommelier/sommelier.h create mode 100644 sommelier/wayland-protocol.gypi create mode 100644 sommelier/wayland_protocol.gni diff --git a/sommelier/BUILD.gn b/sommelier/BUILD.gn new file mode 100644 index 0000000..4a63d7f --- /dev/null +++ b/sommelier/BUILD.gn @@ -0,0 +1,145 @@ +# Copyright 2019 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. + +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 \\\"\\\"\"" +# } +# 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 \\\"\\\"\"" +# } +} + +# Set this to the Xwayland path. +if (!defined(xwayland_path)) { + xwayland_path = "\"/opt/google/cros-containers/bin/Xwayland\"" +} + +# Set this to the GL driver path to use for Xwayland. +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\"" +} + +# Set this to the dark frame color to use for Xwayland clients. +if (!defined(dark_frame_color)) { + dark_frame_color = "\"#323639\"" +} + +wayland_protocol_library("sommelier-protocol") { + out_dir = "include" + sources = [ + "protocol/aura-shell.xml", + "protocol/drm.xml", + "protocol/gtk-shell.xml", + "protocol/keyboard-extension-unstable-v1.xml", + "protocol/linux-dmabuf-unstable-v1.xml", + "protocol/relative-pointer-unstable-v1.xml", + "protocol/text-input-unstable-v1.xml", + "protocol/viewporter.xml", + "protocol/xdg-shell-unstable-v6.xml", + ] +} + +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-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}", + ] +} + +executable("wayland_demo") { + pkg_deps = [ + "libbrillo-${libbase_ver}", + "libchrome-${libbase_ver}", + "wayland-client", + ] + libs = [ "wayland-client" ] + sources = [ + "demos/wayland_demo.cc", + ] +} + +executable("x11_demo") { + pkg_deps = [ + "libbrillo-${libbase_ver}", + "libchrome-${libbase_ver}", + ] + libs = [ "X11" ] + sources = [ + "demos/x11_demo.cc", + ] +} diff --git a/sommelier/Makefile b/sommelier/Makefile new file mode 100644 index 0000000..2b40b54 --- /dev/null +++ b/sommelier/Makefile @@ -0,0 +1,129 @@ +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 +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 +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 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 + +#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 new file mode 100644 index 0000000..e017d9c --- /dev/null +++ b/sommelier/OWNERS @@ -0,0 +1,5 @@ +set noparent +reveman@chromium.org +hollingum@google.com +sidereal@google.com +davidriley@chromium.org diff --git a/sommelier/README.md b/sommelier/README.md new file mode 100644 index 0000000..82d9907 --- /dev/null +++ b/sommelier/README.md @@ -0,0 +1,247 @@ +# Sommelier - Nested Wayland compositor with support for X11 forwarding + +Sommelier is an implementation of a Wayland compositor that delegates +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. + +## Sommeliers + +### Master Sommelier + +The master 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. + +### X11 Sommelier + +An X11 sommelier instance provides X11 forwarding. Xwayland is used to +accomplish this. A single X11 sommelier instance is typically shared across +all X11 clients as they often expect that they can use a shared X server for +communication. If the X11 sommelier instance crashes in this setup, it takes +all running X11 programs down with it. Multiple X11 sommelier instances +can be used for improved isolation or when per-client configuration is +needed, but it will be at the cost of losing the ability for programs to use +the X server for communication between each other. + +### Peer Sommelier + +Each Linux program that support the Wayland protocol can have its own sommelier. +This provides better use of multiple cores when servicing clients, and it +prevents errors in one client from causing other clients to crash. + +## Host Compositor Channel + +Sommelier needs a channel to the host compositor in order to serve Wayland +clients inside a container. If the container environment provides a socket +that can be used to establish a connection to the host compositor, then +pointing sommelier to this socket using the `--display=DISPLAY` flag is +sufficient. + +### VirtWL + +The VirtWL device can be used to establish a new connection when no socket +is available (typically when running inside a VM). If a VirtWL device has been +specified (e.g. `--virtwl-device=/dev/wl0`) then sommelier will use this +mechanism by default to establish new channels between the host compositor and +sommelier instances. Data is forwarded between the VirtWL device and the core +Wayland dispatch mechanism using non-blocking I/O multiplexing. + +## Shared Memory Drivers + +Shared memory allocated inside a container cannot always be shared with the +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 +surface, and copies minimal damaged areas from the client’s standard shared +memory buffers into the virtwl buffers that can be shared with the host +compositor. + +### VirtWL-DMABuf + +The `virtwl-dmabuf` works the same way as the `virtwl` driver but allocates +buffers that can be shared with the host compositor using the linux_dmabuf +protocol. The benefits of using this driver over the basic `virtwl` driver +are: + +* Larger set of supported formats (E.g NV12). +* 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 +damage tracking in order to update intermediate buffers. + +### Surface Buffer Queue + +Each client surface in sommelier is associated with a buffer queue. Each +buffer in the buffer queue has a region (list of rectangles) that describes +the part of the buffer that is damaged compared to the last frame submitted +by the client. This provides high precision damage tracking across multiple +frames. Each new frame from the client adds damage to existing buffers. When +submitting a frame to the host compositor, the next available buffer is +dequeued and updated to not contain any damage. This is done by copying +contents from the current client buffer into the dequeued buffer. + +The client's buffer is released as soon as this copy operation described above +is complete and the client can then reuse the shared memory buffer for another +frame. + +Note: It is important to release the buffer immediately as clients don’t +expect it to be held by the compositor for long when using shared memory. + +### Back Pressure + +Sommelier doesn’t provide any back pressure for when the client is producing +contents faster than the host compositor can consume it. The size of the +buffer queue can as a result grow large. This is not a problem as Xwayland +and other clients handle back pressure themselves using Wayland frame +callbacks or similar mechanism. + +## Data Drivers + +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 +compositor and forwards all data received over this pipe to the client FD. +Forwarding is done using non-blocking I/O multiplexing. + +## Flags and Settings + +Sommelier has two forms of configuration. Command line flags and environment +variables. Standard practice is to expose each option both as a command line +flag and as an environment variable. Command line flags will always override +the configuration provided by environment variables. This makes it easy to +run sommelier as a systemd service and allow the system-wide configuration +to be overridden using a local user provided systemd override file. + +## Density and Scaling + +A protocol aware proxy compositor between the client and the host compositor +makes it easier to support Linux programs that lack good HiDPI support. +It can also be used to adjust the scale of contents to support the dynamic +density changes that Chrome OS UI provide, and it gives the user an option +override any density decisions made by the host compositor. For example, +HiDPI aware programs can run at native display resolution, while some older +programs can use half of that resolution. + +### Contents Scaling + +Contents scaling can be applied to both native wayland clients and X11 +clients. It can be controlled using the `--scale=SCALE` flag or +`SOMMELIER_SCALE=SCALE` variable. Where `SCALE` is a display density +multiplier. For example, if the default density is 200 DPI, then using +`--scale=0.5` will result in contents produced for 100 DPI. + +### Scale Factor + +An optimal scale factor is calculated for Wayland clients based on contents +scale setting and the current host compositor scaling. This allows Wayland +clients to produce contents at an optimal resolution for all combinations of +scaling used by sommelier and the host compositor. + +### DPI + +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 +sommelier should use can be specified with `--dpi=[DPI[,DPI...]]`. Where, +`--dpi=””` will result in sommelier exposing the exact DPI value to clients. + +### XCursor + +Sommelier will set `XCURSOR_SIZE` environment variable automatically based on +the contents scale and preferred host compositor scale factor. + +## Accelerators + +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). + +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 +want the host compositor to handle needs to be explicitly listed as an +accelerator. For example, on Chrome OS, the launcher can be brought up using +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. +`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 +must be followed by an XKB keysym. The `xev` utility can be used to determine +what the XKB keysym is for a specific key. Given the launcher button example +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 +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: + +``` +sommelier --master --socket=wayland-1 +``` + +Start sommelier that runs weston-terminal with density scale multiplier 1.5: + +``` +sommelier --scale=1.5 weston-terminal +``` + +Start sommelier that runs inkscape with density scale multiplier 0.75 and 120 +dots per inch (note that -X is specified as inkscape is an X11 client and +requires X11 forwarding): + +``` +sommelier -X --scale=0.75 --dpi=120 inkscape +``` + +Start sommelier that runs gedit with some accelerators reserved to the host +compositor instead of being sent to gedit: + +``` +sommelier --accelerators="Bracketright,Bracketleft" gedit +``` diff --git a/sommelier/build/args.gn b/sommelier/build/args.gn new file mode 100644 index 0000000..5fc774c --- /dev/null +++ b/sommelier/build/args.gn @@ -0,0 +1,8 @@ +# 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/config.h b/sommelier/config.h new file mode 100644 index 0000000..7f9f75b --- /dev/null +++ b/sommelier/config.h @@ -0,0 +1,16 @@ +#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 new file mode 100644 index 0000000..a5c9d1b --- /dev/null +++ b/sommelier/demos/wayland_demo.cc @@ -0,0 +1,319 @@ +// 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 new file mode 100644 index 0000000..3345a73 --- /dev/null +++ b/sommelier/demos/x11_demo.cc @@ -0,0 +1,90 @@ +// 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 new file mode 100644 index 0000000..1d3ec6b --- /dev/null +++ b/sommelier/linux/virtio_wl.h @@ -0,0 +1,135 @@ +#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/linux/virtwl.h b/sommelier/linux/virtwl.h new file mode 100644 index 0000000..9390413 --- /dev/null +++ b/sommelier/linux/virtwl.h @@ -0,0 +1,64 @@ +#ifndef _LINUX_VIRTWL_H +#define _LINUX_VIRTWL_H + +#include +#include + +#define VIRTWL_SEND_MAX_ALLOCS 28 + +#define VIRTWL_IOCTL_BASE 'w' +#define VIRTWL_IO(nr) _IO(VIRTWL_IOCTL_BASE, nr) +#define VIRTWL_IOR(nr, type) _IOR(VIRTWL_IOCTL_BASE, nr, type) +#define VIRTWL_IOW(nr, type) _IOW(VIRTWL_IOCTL_BASE, nr, type) +#define VIRTWL_IOWR(nr, type) _IOWR(VIRTWL_IOCTL_BASE, nr, type) + +enum virtwl_ioctl_new_type { + VIRTWL_IOCTL_NEW_CTX, /* open a new wayland connection context */ + VIRTWL_IOCTL_NEW_ALLOC, /* create a new virtwl shm allocation */ + /* create a new virtwl pipe that is readable via the returned fd */ + VIRTWL_IOCTL_NEW_PIPE_READ, + /* create a new virtwl pipe that is writable via the returned fd */ + VIRTWL_IOCTL_NEW_PIPE_WRITE, + /* create a new virtwl dmabuf that is writable via the returned fd */ + VIRTWL_IOCTL_NEW_DMABUF, +}; + +struct virtwl_ioctl_new { + __u32 type; /* VIRTWL_IOCTL_NEW_* */ + int fd; /* return fd */ + __u32 flags; /* currently always 0 */ + union { + /* size of allocation if type == VIRTWL_IOCTL_NEW_ALLOC */ + __u32 size; + /* buffer description if type == VIRTWL_IOCTL_NEW_DMABUF */ + struct { + __u32 width; /* width in pixels */ + __u32 height; /* height in pixels */ + __u32 format; /* fourcc format */ + __u32 stride0; /* return stride0 */ + __u32 stride1; /* return stride1 */ + __u32 stride2; /* return stride2 */ + __u32 offset0; /* return offset0 */ + __u32 offset1; /* return offset1 */ + __u32 offset2; /* return offset2 */ + } dmabuf; + }; +}; + +struct virtwl_ioctl_txn { + int fds[VIRTWL_SEND_MAX_ALLOCS]; + __u32 len; + __u8 data[0]; +}; + +struct virtwl_ioctl_dmabuf_sync { + __u32 flags; /* synchronization flags (see dma-buf.h) */ +}; + +#define VIRTWL_IOCTL_NEW VIRTWL_IOWR(0x00, struct virtwl_ioctl_new) +#define VIRTWL_IOCTL_SEND VIRTWL_IOR(0x01, struct virtwl_ioctl_txn) +#define VIRTWL_IOCTL_RECV VIRTWL_IOW(0x02, struct virtwl_ioctl_txn) +#define VIRTWL_IOCTL_DMABUF_SYNC VIRTWL_IOR(0x03, \ + struct virtwl_ioctl_dmabuf_sync) + +#endif /* _LINUX_VIRTWL_H */ diff --git a/sommelier/protocol/aura-shell.xml b/sommelier/protocol/aura-shell.xml new file mode 100644 index 0000000..319d1b2 --- /dev/null +++ b/sommelier/protocol/aura-shell.xml @@ -0,0 +1,238 @@ + + + + + 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"), + 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. + + + + + The global interface exposing aura shell capabilities is used to + instantiate an interface extension for a wl_surface object. + This extended interface will then allow the client to use aura shell + specific functionality. + + + + + + + + + + Instantiate an interface extension for the given wl_surface to + provide aura shell functionality. If the given wl_surface is not + associated with a shell surface, the shell_surface_missing protocol + error is raised. + + + + + + + + + + + Instantiate an interface extension for the given wl_output to + provide aura shell functionality. + + + + + + + + + + An additional interface to a wl_surface object, which allows the + client to access aura shell specific functionality for surface. + + + + + Frame types that can be used to decorate a surface. + + + + + + + + + Suggests a surface should use a specific frame. + + + + + + + + + Set the "parent" of this surface. "x" and "y" arguments specify the + initial position for surface relative to parent. + + + + + + + + + + + Set the frame colors. + + + + + + + + + + Set the startup ID. + + + + + + + + + Set the application ID. + + + + + + + + An additional interface to a wl_output object, which allows the + client to access aura shell specific functionality for output. + + + + + + + These flags describe properties of an output scale. + They are used in the flags bitfield of the scale event. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The scale event describes an available scale for the output. + + The event is sent when binding to the output object and there + will always be one scale, the current scale. The event is sent + again if an output changes scale, for the scale that is now + current. In other words, the current scale is always the last + scale that was received with the current flag set. + + + + + + + + + + + + + + + The connection event describes how the output is connected. + + The event is sent when binding to the output object. + + + + + + + This event describes the device specific scale factor for the output. + + The device specific scale factor is not expected the change during + the lifetime of the output. And it is not limited to an integer value + like the scale factor provided by wl_output interface. The exact + contents scale used by the compositor can be determined by combining + this device scale factor with the current output scale. + + The event is sent when binding to the output object. + + + + + + diff --git a/sommelier/protocol/drm.xml b/sommelier/protocol/drm.xml new file mode 100644 index 0000000..8a3ad69 --- /dev/null +++ b/sommelier/protocol/drm.xml @@ -0,0 +1,182 @@ + + + + + Copyright © 2008-2011 Kristian Høgsberg + Copyright © 2010-2011 Intel Corporation + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that\n the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Bitmask of capabilities. + + + + + + + + + + diff --git a/sommelier/protocol/gtk-shell.xml b/sommelier/protocol/gtk-shell.xml new file mode 100644 index 0000000..5cfdd42 --- /dev/null +++ b/sommelier/protocol/gtk-shell.xml @@ -0,0 +1,61 @@ + + + + + gtk_shell is a protocol extension providing additional features for + clients implementing it. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sommelier/protocol/keyboard-extension-unstable-v1.xml b/sommelier/protocol/keyboard-extension-unstable-v1.xml new file mode 100644 index 0000000..a90604d --- /dev/null +++ b/sommelier/protocol/keyboard-extension-unstable-v1.xml @@ -0,0 +1,82 @@ + + + + + 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"), + 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 wl_keyboard to send ack_key requests for each key event of + the keyboard to the server. + + 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. + + + + + + + + + Create extended_keyboard object. + See zcr_extended_keyboard interface for details. + If the given wl_keyboard object already has a extended_keyboard object + associated, the extended_keyboard_exists protocol error is raised. + + + + + + + + + The zcr_extended_keyboard_v1 interface extends the wl_keyboard interface + with requests to notify whether sent key events are handled or not by + the client. + + + + + + + + + + + + + + + + + + + diff --git a/sommelier/protocol/linux-dmabuf-unstable-v1.xml b/sommelier/protocol/linux-dmabuf-unstable-v1.xml new file mode 100644 index 0000000..154afe2 --- /dev/null +++ b/sommelier/protocol/linux-dmabuf-unstable-v1.xml @@ -0,0 +1,348 @@ + + + + + Copyright © 2014, 2015 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. + + + + + Following the interfaces from: + https://www.khronos.org/registry/egl/extensions/EXT/EGL_EXT_image_dma_buf_import.txt + and the Linux DRM sub-system's AddFb2 ioctl. + + This interface offers ways to create generic dmabuf-based + wl_buffers. Immediately after a client binds to this interface, + the set of supported formats and format modifiers is sent with + 'format' and 'modifier' events. + + The following are required from clients: + + - Clients must ensure that either all data in the dma-buf is + coherent for all subsequent read access or that coherency is + correctly handled by the underlying kernel-side dma-buf + implementation. + + - Don't make any more attachments after sending the buffer to the + compositor. Making more attachments later increases the risk of + the compositor not being able to use (re-import) an existing + dmabuf-based wl_buffer. + + The underlying graphics stack must ensure the following: + + - The dmabuf file descriptors relayed to the server will stay valid + for the whole lifetime of the wl_buffer. This means the server may + at any time use those fds to import the dmabuf into any kernel + sub-system that might accept it. + + To create a wl_buffer from one or more dmabufs, a client creates a + zwp_linux_dmabuf_params_v1 object with a zwp_linux_dmabuf_v1.create_params + request. All planes required by the intended format are added with + the 'add' request. Finally, a 'create' or 'create_immed' request is + issued, which has the following outcome depending on the import success. + + The 'create' request, + - on success, triggers a 'created' event which provides the final + wl_buffer to the client. + - on failure, triggers a 'failed' event to convey that the server + cannot use the dmabufs received from the client. + + For the 'create_immed' request, + - on success, the server immediately imports the added dmabufs to + create a wl_buffer. No event is sent from the server in this case. + - on failure, the server can choose to either: + - terminate the client by raising a fatal error. + - mark the wl_buffer as failed, and send a 'failed' event to the + client. If the client uses a failed wl_buffer as an argument to any + request, the behaviour is compositor implementation-defined. + + 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. + + + + + Objects created through this interface, especially wl_buffers, will + remain valid. + + + + + + This temporary object is used to collect multiple dmabuf handles into + a single batch to create a wl_buffer. It can only be used once and + should be destroyed after a 'created' or 'failed' event has been + received. + + + + + + + This event advertises one buffer format that the server supports. + All the supported formats are advertised once when the client + binds to this interface. A roundtrip after binding guarantees + that the client has received all supported formats. + + For the definition of the format codes, see the + zwp_linux_buffer_params_v1::create request. + + Warning: the 'format' event is likely to be deprecated and replaced + with the 'modifier' event introduced in zwp_linux_dmabuf_v1 + version 3, described below. Please refrain from using the information + received from this event. + + + + + + + This event advertises the formats that the server supports, along with + the modifiers supported for each format. All the supported modifiers + for all the supported formats are advertised once when the client + binds to this interface. A roundtrip after binding guarantees that + the client has received all supported format-modifier pairs. + + For the definition of the format and modifier codes, see the + zwp_linux_buffer_params_v1::create request. + + + + + + + + + + This temporary object is a collection of dmabufs and other + parameters that together form a single logical buffer. The temporary + object may eventually create one wl_buffer unless cancelled by + destroying it before requesting 'create'. + + Single-planar formats only require one dmabuf, however + multi-planar formats may require more than one dmabuf. For all + formats, an 'add' request must be called once per plane (even if the + underlying dmabuf fd is identical). + + You must use consecutive plane indices ('plane_idx' argument for 'add') + from zero to the number of planes used by the drm_fourcc format code. + All planes required by the format must be given exactly once, but can + be given in any order. Each plane index can be set only once. + + + + + + + + + + + + + + + + Cleans up the temporary data sent to the server for dmabuf-based + wl_buffer creation. + + + + + + This request adds one dmabuf to the set in this + zwp_linux_buffer_params_v1. + + The 64-bit unsigned value combined from modifier_hi and modifier_lo + is the dmabuf layout modifier. DRM AddFB2 ioctl calls this the + fb modifier, which is defined in drm_mode.h of Linux UAPI. + This is an opaque token. Drivers use this token to express tiling, + compression, etc. driver-specific modifications to the base format + defined by the DRM fourcc code. + + This request raises the PLANE_IDX error if plane_idx is too large. + The error PLANE_SET is raised if attempting to set a plane that + was already set. + + + + + + + + + + + + + + + + + + This asks for creation of a wl_buffer from the added dmabuf + buffers. The wl_buffer is not created immediately but returned via + the 'created' event if the dmabuf sharing succeeds. The sharing + may fail at runtime for reasons a client cannot predict, in + which case the 'failed' event is triggered. + + The 'format' argument is a DRM_FORMAT code, as defined by the + libdrm's drm_fourcc.h. The Linux kernel's DRM sub-system is the + authoritative source on how the format codes should work. + + The 'flags' is a bitfield of the flags defined in enum "flags". + 'y_invert' means the that the image needs to be y-flipped. + + Flag 'interlaced' means that the frame in the buffer is not + progressive as usual, but interlaced. An interlaced buffer as + supported here must always contain both top and bottom fields. + The top field always begins on the first pixel row. The temporal + ordering between the two fields is top field first, unless + 'bottom_first' is specified. It is undefined whether 'bottom_first' + is ignored if 'interlaced' is not set. + + This protocol does not convey any information about field rate, + duration, or timing, other than the relative ordering between the + two fields in one buffer. A compositor may have to estimate the + intended field rate from the incoming buffer rate. It is undefined + whether the time of receiving wl_surface.commit with a new buffer + attached, applying the wl_surface state, wl_surface.frame callback + trigger, presentation, or any other point in the compositor cycle + is used to measure the frame or field times. There is no support + for detecting missed or late frames/fields/buffers either, and + there is no support whatsoever for cooperating with interlaced + compositor output. + + The composited image quality resulting from the use of interlaced + buffers is explicitly undefined. A compositor may use elaborate + hardware features or software to deinterlace and create progressive + output frames from a sequence of interlaced input buffers, or it + may produce substandard image quality. However, compositors that + cannot guarantee reasonable image quality in all cases are recommended + to just reject all interlaced buffers. + + Any argument errors, including non-positive width or height, + mismatch between the number of planes and the format, bad + format, bad offset or stride, may be indicated by fatal protocol + errors: INCOMPLETE, INVALID_FORMAT, INVALID_DIMENSIONS, + OUT_OF_BOUNDS. + + Dmabuf import errors in the server that are not obvious client + bugs are returned via the 'failed' event as non-fatal. This + allows attempting dmabuf sharing and falling back in the client + if it fails. + + This request can be sent only once in the object's lifetime, after + which the only legal request is destroy. This object should be + destroyed after issuing a 'create' request. Attempting to use this + object after issuing 'create' raises ALREADY_USED protocol error. + + It is not mandatory to issue 'create'. If a client wants to + cancel the buffer creation, it can just destroy this object. + + + + + + + + + + This event indicates that the attempted buffer creation was + successful. It provides the new wl_buffer referencing the dmabuf(s). + + Upon receiving this event, the client should destroy the + zlinux_dmabuf_params object. + + + + + + + This event indicates that the attempted buffer creation has + failed. It usually means that one of the dmabuf constraints + has not been fulfilled. + + Upon receiving this event, the client should destroy the + zlinux_buffer_params object. + + + + + + This asks for immediate creation of a wl_buffer by importing the + added dmabufs. + + In case of import success, no event is sent from the server, and the + wl_buffer is ready to be used by the client. + + Upon import failure, either of the following may happen, as seen fit + by the implementation: + - the client is terminated with one of the following fatal protocol + errors: + - INCOMPLETE, INVALID_FORMAT, INVALID_DIMENSIONS, OUT_OF_BOUNDS, + in case of argument errors such as mismatch between the number + of planes and the format, bad format, non-positive width or + height, or bad offset or stride. + - INVALID_WL_BUFFER, in case the cause for failure is unknown or + plaform specific. + - the server creates an invalid wl_buffer, marks it as failed and + sends a 'failed' event to the client. The result of using this + invalid wl_buffer as an argument in any request by the client is + defined by the compositor implementation. + + This takes the same arguments as a 'create' request, and obeys the + same restrictions. + + + + + + + + + + + diff --git a/sommelier/protocol/relative-pointer-unstable-v1.xml b/sommelier/protocol/relative-pointer-unstable-v1.xml new file mode 100644 index 0000000..753a56a --- /dev/null +++ b/sommelier/protocol/relative-pointer-unstable-v1.xml @@ -0,0 +1,136 @@ + + + + + Copyright © 2014 Jonas Ådahl + Copyright © 2015 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 specifies a set of interfaces used for making clients able to + receive relative pointer events not obstructed by barriers (such as the + monitor edge or other pointer barriers). + + To start receiving relative pointer events, a client must first bind the + global interface "wp_relative_pointer_manager" which, if a compositor + supports relative pointer motion events, is exposed by the registry. After + having created the relative pointer manager proxy object, the client uses + it to create the actual relative pointer object using the + "get_relative_pointer" request given a wl_pointer. The relative pointer + motion events will then, when applicable, be transmitted via the proxy of + the newly created relative pointer object. See the documentation of the + relative pointer interface for more details. + + 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 interface used for getting the relative pointer object for a + given pointer. + + + + + Used by the client to notify the server that it will no longer use this + relative pointer manager object. + + + + + + Create a relative pointer interface given a wl_pointer object. See the + wp_relative_pointer interface for more details. + + + + + + + + + A wp_relative_pointer object is an extension to the wl_pointer interface + used for emitting relative pointer events. It shares the same focus as + wl_pointer objects of the same seat and will only emit events when it has + focus. + + + + + + + + + Relative x/y pointer motion from the pointer of the seat associated with + this object. + + A relative motion is in the same dimension as regular wl_pointer motion + events, except they do not represent an absolute position. For example, + moving a pointer from (x, y) to (x', y') would have the equivalent + relative motion (x' - x, y' - y). If a pointer motion caused the + absolute pointer position to be clipped by for example the edge of the + monitor, the relative motion is unaffected by the clipping and will + represent the unclipped motion. + + This event also contains non-accelerated motion deltas. The + non-accelerated delta is, when applicable, the regular pointer motion + delta as it was before having applied motion acceleration and other + transformations such as normalization. + + Note that the non-accelerated delta does not represent 'raw' events as + they were read from some device. Pointer motion acceleration is device- + and configuration-specific and non-accelerated deltas and accelerated + deltas may have the same value on some devices. + + Relative motions are not coupled to wl_pointer.motion events, and can be + sent in combination with such events, but also independently. There may + also be scenarios where wl_pointer.motion is sent, but there is no + relative motion. The order of an absolute and relative motion event + originating from the same physical motion is not guaranteed. + + If the client needs button events or focus state, it can receive them + from a wl_pointer object of the same seat that the wp_relative_pointer + object is associated with. + + + + + + + + + + + diff --git a/sommelier/protocol/text-input-unstable-v1.xml b/sommelier/protocol/text-input-unstable-v1.xml new file mode 100644 index 0000000..29a217e --- /dev/null +++ b/sommelier/protocol/text-input-unstable-v1.xml @@ -0,0 +1,385 @@ + + + + + Copyright © 2012, 2013 Intel Corporation + + 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. + + + + + An object used for text input. Adds support for text input and input + methods to applications. A text_input object is created from a + wl_text_input_manager and corresponds typically to a text entry in an + application. + + Requests are used to activate/deactivate the text_input object and set + state information like surrounding and selected text or the content type. + The information about entered text is sent to the text_input object via + the pre-edit and commit events. Using this interface removes the need + for applications to directly process hardware key events and compose text + out of them. + + Text is generally UTF-8 encoded, indices and lengths are in bytes. + + Serials are used to synchronize the state between the text input and + an input method. New serials are sent by the text input in the + commit_state request and are used by the input method to indicate + the known text input state in events like preedit_string, commit_string, + and keysym. The text input can then ignore events from the input method + which are based on an outdated state (for example after a reset). + + 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. + + + + + Requests the text_input object to be activated (typically when the + text entry gets focus). + + The seat argument is a wl_seat which maintains the focus for this + activation. The surface argument is a wl_surface assigned to the + text_input object and tracked for focus lost. The enter event + is emitted on successful activation. + + + + + + + + Requests the text_input object to be deactivated (typically when the + text entry lost focus). The seat argument is a wl_seat which was used + for activation. + + + + + + + Requests input panels (virtual keyboard) to show. + + + + + + Requests input panels (virtual keyboard) to hide. + + + + + + Should be called by an editor widget when the input state should be + reset, for example after the text was changed outside of the normal + input method flow. + + + + + + Sets the plain surrounding text around the input position. Text is + UTF-8 encoded. Cursor is the byte offset within the + surrounding text. Anchor is the byte offset of the + selection anchor within the surrounding text. If there is no selected + text anchor, then it is the same as cursor. + + + + + + + + + Content hint is a bitmask to allow to modify the behavior of the text + input. + + + + + + + + + + + + + + + + + + + The content purpose allows to specify the primary purpose of a text + input. + + This allows an input method to show special purpose input panels with + extra characters or to disallow some characters. + + + + + + + + + + + + + + + + + + + Sets the content purpose and content hint. While the purpose is the + basic purpose of an input field, the hint flags allow to modify some + of the behavior. + + When no content type is explicitly set, a normal content purpose with + default hints (auto completion, auto correction, auto capitalization) + should be assumed. + + + + + + + + + + + + + + + Sets a specific language. This allows for example a virtual keyboard to + show a language specific layout. The "language" argument is an RFC-3066 + format language tag. + + It could be used for example in a word processor to indicate the + language of the currently edited document or in an instant message + application which tracks languages of contacts. + + + + + + + + + + + + + + + + Notify the text_input object when it received focus. Typically in + response to an activate request. + + + + + + + Notify the text_input object when it lost focus. Either in response + to a deactivate request or when the assigned surface lost focus or was + destroyed. + + + + + + Transfer an array of 0-terminated modifier names. The position in + the array is the index of the modifier as used in the modifiers + bitmask in the keysym event. + + + + + + + Notify when the visibility state of the input panel changed. + + + + + + + Notify when a new composing text (pre-edit) should be set around the + current cursor position. Any previously set composing text should + be removed. + + The commit text can be used to replace the preedit text on reset + (for example on unfocus). + + The text input should also handle all preedit_style and preedit_cursor + events occurring directly before preedit_string. + + + + + + + + + + + + + + + + + + + + Sets styling information on composing text. The style is applied for + length bytes from index relative to the beginning of the composing + text (as byte offset). Multiple styles can + be applied to a composing text by sending multiple preedit_styling + events. + + This event is handled as part of a following preedit_string event. + + + + + + + + + Sets the cursor position inside the composing text (as byte + offset) relative to the start of the composing text. When index is a + negative number no cursor is shown. + + This event is handled as part of a following preedit_string event. + + + + + + + Notify when text should be inserted into the editor widget. The text to + commit could be either just a single character after a key press or the + result of some composing (pre-edit). It could also be an empty text + when some text should be removed (see delete_surrounding_text) or when + the input cursor should be moved (see cursor_position). + + Any previously set composing text should be removed. + + + + + + + + Notify when the cursor or anchor position should be modified. + + This event should be handled as part of a following commit_string + event. + + + + + + + + Notify when the text around the current cursor position should be + deleted. + + Index is relative to the current cursor (in bytes). + Length is the length of deleted text (in bytes). + + This event should be handled as part of a following commit_string + event. + + + + + + + + Notify when a key event was sent. Key events should not be used + for normal text input operations, which should be done with + commit_string, delete_surrounding_text, etc. The key event follows + the wl_keyboard key event convention. Sym is an XKB keysym, state a + wl_keyboard key_state. Modifiers are a mask for effective modifiers + (where the modifier indices are set by the modifiers_map event) + + + + + + + + + + + Sets the language of the input text. The "language" argument is an + RFC-3066 format language tag. + + + + + + + + + + + + + + Sets the text direction of input text. + + It is mainly needed for showing an input cursor on the correct side of + the editor when there is no input done yet and making sure neutral + direction text is laid out properly. + + + + + + + + + A factory for text_input objects. This object is a global singleton. + + + + + Creates a new text_input object. + + + + + + diff --git a/sommelier/protocol/viewporter.xml b/sommelier/protocol/viewporter.xml new file mode 100644 index 0000000..c732d8c --- /dev/null +++ b/sommelier/protocol/viewporter.xml @@ -0,0 +1,186 @@ + + + + + Copyright © 2013-2016 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. + + + + + The global interface exposing surface cropping and scaling + capabilities is used to instantiate an interface extension for a + wl_surface object. This extended interface will then allow + cropping and scaling the surface contents, effectively + disconnecting the direct relationship between the buffer and the + surface size. + + + + + Informs the server that the client will not be using this + protocol object anymore. This does not affect any other objects, + wp_viewport objects included. + + + + + + + + + + Instantiate an interface extension for the given wl_surface to + crop and scale its content. If the given wl_surface already has + a wp_viewport object associated, the viewport_exists + protocol error is raised. + + + + + + + + + An additional interface to a wl_surface object, which allows the + client to specify the cropping and scaling of the surface + contents. + + This interface works with two concepts: the source rectangle (src_x, + src_y, src_width, src_height), and the destination size (dst_width, + dst_height). The contents of the source rectangle are scaled to the + destination size, and content outside the source rectangle is ignored. + This state is double-buffered, and is applied on the next + wl_surface.commit. + + The two parts of crop and scale state are independent: the source + rectangle, and the destination size. Initially both are unset, that + is, no scaling is applied. The whole of the current wl_buffer is + used as the source, and the surface size is as defined in + wl_surface.attach. + + If the destination size is set, it causes the surface size to become + dst_width, dst_height. The source (rectangle) is scaled to exactly + this size. This overrides whatever the attached wl_buffer size is, + unless the wl_buffer is NULL. If the wl_buffer is NULL, the surface + has no content and therefore no size. Otherwise, the size is always + at least 1x1 in surface local coordinates. + + If the source rectangle is set, it defines what area of the wl_buffer is + taken as the source. If the source rectangle is set and the destination + size is not set, then src_width and src_height must be integers, and the + surface size becomes the source rectangle size. This results in cropping + without scaling. If src_width or src_height are not integers and + destination size is not set, the bad_size protocol error is raised when + the surface state is applied. + + The coordinate transformations from buffer pixel coordinates up to + the surface-local coordinates happen in the following order: + 1. buffer_transform (wl_surface.set_buffer_transform) + 2. buffer_scale (wl_surface.set_buffer_scale) + 3. crop and scale (wp_viewport.set*) + This means, that the source rectangle coordinates of crop and scale + are given in the coordinates after the buffer transform and scale, + i.e. in the coordinates that would be the surface-local coordinates + if the crop and scale was not applied. + + If src_x or src_y are negative, the bad_value protocol error is raised. + Otherwise, if the source rectangle is partially or completely outside of + the non-NULL wl_buffer, then the out_of_buffer protocol error is raised + when the surface state is applied. A NULL wl_buffer does not raise the + out_of_buffer error. + + The x, y arguments of wl_surface.attach are applied as normal to + the surface. They indicate how many pixels to remove from the + surface size from the left and the top. In other words, they are + still in the surface-local coordinate system, just like dst_width + and dst_height are. + + If the wl_surface associated with the wp_viewport is destroyed, + all wp_viewport requests except 'destroy' raise the protocol error + no_surface. + + If the wp_viewport object is destroyed, the crop and scale + state is removed from the wl_surface. The change will be applied + on the next wl_surface.commit. + + + + + The associated wl_surface's crop and scale state is removed. + The change is applied on the next wl_surface.commit. + + + + + + + + + + + + + Set the source rectangle of the associated wl_surface. See + wp_viewport for the description, and relation to the wl_buffer + size. + + If all of x, y, width and height are -1.0, the source rectangle is + unset instead. Any other set of values where width or height are zero + or negative, or x or y are negative, raise the bad_value protocol + error. + + The crop and scale state is double-buffered state, and will be + applied on the next wl_surface.commit. + + + + + + + + + + Set the destination size of the associated wl_surface. See + wp_viewport for the description, and relation to the wl_buffer + size. + + If width is -1 and height is -1, the destination size is unset + instead. Any other pair of values for width and height that + contains zero or negative values raises the bad_value protocol + error. + + The crop and scale state is double-buffered state, and will be + applied on the next wl_surface.commit. + + + + + + + diff --git a/sommelier/protocol/xdg-shell-unstable-v6.xml b/sommelier/protocol/xdg-shell-unstable-v6.xml new file mode 100644 index 0000000..1c0f924 --- /dev/null +++ b/sommelier/protocol/xdg-shell-unstable-v6.xml @@ -0,0 +1,1044 @@ + + + + + Copyright © 2008-2013 Kristian Høgsberg + Copyright © 2013 Rafael Antognolli + Copyright © 2013 Jasper St. Pierre + Copyright © 2010-2013 Intel Corporation + + 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. + + + + + 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. + + + + + + + + + + + + + + Destroy this xdg_shell object. + + Destroying a bound xdg_shell object while there are surfaces + still alive created by this xdg_shell object instance is illegal + and will result in a protocol error. + + + + + + Create a positioner object. A positioner object is used to position + surfaces relative to some parent surface. See the interface description + and xdg_surface.get_popup for details. + + + + + + + This creates an xdg_surface for the given surface. While xdg_surface + itself is not a role, the corresponding surface may only be assigned + a role extending xdg_surface, such as xdg_toplevel or xdg_popup. + + This creates an xdg_surface for the given surface. An xdg_surface is + used as basis to define a role to a given surface, such as xdg_toplevel + or xdg_popup. It also manages functionality shared between xdg_surface + based surface roles. + + 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 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. + + Compositors can use this to determine if the client is still + alive. It's unspecified what will happen if the client doesn't + respond to the ping request, or in what timeframe. Clients should + 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. + + + + + + + + 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 + the child surface remains within the visible area's borders, and to + specify how the child surface changes its position, such as sliding along + an axis, or flipping around a rectangle. These positioner-created rules are + constrained by the requirement that a child surface must intersect with or + be at least partially adjacent to its parent surface. + + See the various requests for details about possible rules. + + At the time of the request, the compositor makes a copy of the rules + specified by the xdg_positioner. Thus, after the request is complete the + xdg_positioner object can be destroyed or reused; further changes to the + object will have no effect on previous usages. + + For an xdg_positioner object to be considered complete, it must have a + non-zero size set by set_size, and a non-zero anchor rectangle set by + set_anchor_rect. Passing an incomplete xdg_positioner object when + positioning a surface raises an error. + + + + + + + + + Notify the compositor that the xdg_positioner will no longer be used. + + + + + + Set the size of the surface that is to be positioned with the positioner + object. The size is in surface-local coordinates and corresponds to the + window geometry. See xdg_surface.set_window_geometry. + + If a zero or negative size is set the invalid_input error is raised. + + + + + + + + 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. + + 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. + + + + + + + + + + + + + + + + + + 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 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 constraint adjustment value define ways the compositor will adjust + the position of the surface, if the unadjusted position would result + in the surface being partly constrained. + + Whether a surface is considered 'constrained' is left to the compositor + to determine. For example, the surface may be partly outside the + compositor's defined 'work area', thus necessitating the child surface's + position be adjusted until it is entirely inside the work area. + + The adjustments can be combined, according to a defined precedence: 1) + Flip, 2) Slide, 3) Resize. + + + + Don't alter the surface position even if it is constrained on some + axis, for example partially outside the edge of a monitor. + + + + + Slide the surface along the x axis until it is no longer constrained. + + First try to slide towards the direction of the gravity on the x axis + until either the edge in the opposite direction of the gravity is + unconstrained or the edge in the direction of the gravity is + constrained. + + Then try to slide towards the opposite direction of the gravity on the + x axis until either the edge in the direction of the gravity is + unconstrained or the edge in the opposite direction of the gravity is + constrained. + + + + + Slide the surface along the y axis until it is no longer constrained. + + First try to slide towards the direction of the gravity on the y axis + until either the edge in the opposite direction of the gravity is + unconstrained or the edge in the direction of the gravity is + constrained. + + Then try to slide towards the opposite direction of the gravity on the + y axis until either the edge in the direction of the gravity is + unconstrained or the edge in the opposite direction of the gravity is + constrained. + + + + + Invert the anchor and gravity on the x axis if the surface is + constrained on the x axis. For example, if the left edge of the + surface is constrained, the gravity is 'left' and the anchor is + 'left', change the gravity to 'right' and the anchor to 'right'. + + If the adjusted position also ends up being constrained, the resulting + position of the flip_x adjustment will be the one before the + adjustment. + + + + + Invert the anchor and gravity on the y axis if the surface is + constrained on the y axis. For example, if the bottom edge of the + surface is constrained, the gravity is 'bottom' and the anchor is + 'bottom', change the gravity to 'top' and the anchor to 'top'. + + If the adjusted position also ends up being constrained, the resulting + position of the flip_y adjustment will be the one before the + adjustment. + + + + + Resize the surface horizontally so that it is completely + unconstrained. + + + + + Resize the surface vertically so that it is completely unconstrained. + + + + + + + Specify how the window should be positioned if the originally intended + position caused the surface to be constrained, meaning at least + partially outside positioning boundaries set by the compositor. The + adjustment is set by constructing a bitmask describing the adjustment to + be made when the surface is constrained on that axis. + + If no bit for one axis is set, the compositor will assume that the child + surface should not change its position on that axis when constrained. + + If more than one bit for one axis is set, the order of how adjustments + are applied is specified in the corresponding adjustment descriptions. + + The default adjustment is none. + + + + + + + Specify the surface position offset relative to the position of the + anchor on the anchor rectangle and the anchor on the surface. For + example if the anchor of the anchor rectangle is at (x, y), the surface + has the gravity bottom|right, and the offset is (ox, oy), the calculated + surface position will be (x + ox, y + oy). The offset position of the + surface is the one used for constraint testing. See + set_constraint_adjustment. + + An example use case is placing a popup menu on top of a user interface + element, while aligning the user interface element of the parent surface + with some user interface element placed somewhere in the popup surface. + + + + + + + + + An interface that may be implemented by a wl_surface, for + implementations that provide a desktop-style user interface. + + It provides a base set of functionality required to construct user + interface elements requiring management by the compositor, such as + toplevel windows, menus, etc. The types of functionality are split into + xdg_surface roles. + + Creating an xdg_surface does not set the role for a wl_surface. In order + to map an xdg_surface, the client must create a role-specific object + using, e.g., get_toplevel, get_popup. The wl_surface for any given + xdg_surface can have at most one role, and may not be assigned any role + not based on xdg_surface. + + A role must be assigned before any other requests are made to the + xdg_surface object. + + The client must call wl_surface.commit on the corresponding wl_surface + for the xdg_surface state to take effect. + + Creating an xdg_surface from a wl_surface which has a buffer attached or + committed is a client error, and any attempts by a client to attach or + 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. + + + + + + + + + + + Destroy the xdg_surface object. An xdg_surface must only be destroyed + after its role object has been destroyed. + + + + + + This creates an xdg_toplevel object for the given xdg_surface and gives + the associated wl_surface the xdg_toplevel role. + + 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. + + See the documentation of xdg_popup for more details about what an + xdg_popup is and how it is used. + + + + + + + + + The window geometry of a surface is its "visible bounds" from the + user's perspective. Client-side decorations often have invisible + portions like drop-shadows which should be ignored for the + purposes of aligning, placing and constraining windows. + + The window geometry is double buffered, and will be applied at the + time wl_surface.commit of the corresponding wl_surface is called. + + 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. + + If never set, the value is the full bounds of the surface, + including any subsurfaces. This updates dynamically on every + commit. This unset is meant for extremely simple clients. + + The arguments are given in the surface-local coordinate space of + the wl_surface associated with this xdg_surface. + + The width and height must be greater than zero. Setting an invalid size + will raise an error. When applied, the effective window geometry will be + the set window geometry clamped to the bounding rectangle of the + combined geometry of the surface of the xdg_surface and the associated + subsurfaces. + + + + + + + + + + When a configure event is received, if a client commits the + surface in response to the configure event, then the client + must make an ack_configure request sometime before the commit + request, passing along the serial of the configure event. + + For instance, for toplevel surfaces the compositor might use this + information to move a surface to the top left only when the client has + drawn itself for the maximized or fullscreen state. + + If the client receives multiple configure events before it + can respond to one, it only has to ack the last configure event. + + A client is not required to commit immediately after sending + an ack_configure request - it may even ack_configure several times + before its next surface commit. + + A client may send multiple ack_configure requests before committing, but + only the last request sent before a commit indicates which configure + event the client really is responding to. + + + + + + + The configure event marks the end of a configure sequence. A configure + sequence is a set of one or more events configuring the state of the + xdg_surface, including the final xdg_surface.configure event. + + Where applicable, xdg_surface surface roles will during a configure + sequence extend this event as a latched state sent as events before the + xdg_surface.configure event. Such events should be considered to make up + a set of atomically applied configuration states, where the + xdg_surface.configure commits the accumulated state. + + Clients should arrange their surface for the new states, and then send + an ack_configure request with the serial sent in this configure event at + some point before committing the new surface. + + If the client receives multiple configure events before it can respond + to one, it is free to discard all but the last event it received. + + + + + + + + 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. + + + + + 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. + + + + + + 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. + + Parent windows should be set on dialogs, toolboxes, or other + "auxiliary" surfaces, so that the parent is raised when the dialog + is raised. + + + + + + + Set a short title for the surface. + + This string may be used to identify the surface in a task bar, + window list, or other user interface elements provided by the + compositor. + + The string must be encoded in UTF-8. + + + + + + + Set an application identifier for the surface. + + The app ID identifies the general class of applications to which + the surface belongs. The compositor can use this to group multiple + surfaces together, or to determine how to launch a new application. + + For D-Bus activatable applications, the app ID is used as the D-Bus + service name. + + The compositor shell will try to group application surfaces together + by their app ID. As a best practice, it is suggested to select app + ID's that match the basename of the application's .desktop file. + For example, "org.freedesktop.FooViewer" where the .desktop file is + "org.freedesktop.FooViewer.desktop". + + 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. + + [0] http://standards.freedesktop.org/desktop-entry-spec/ + + + + + + + Clients implementing client-side decorations might want to show + a context menu when right-clicking on the decorations, giving the + user a menu that they can use to maximize or minimize the window. + + This request asks the compositor to pop up such a window menu at + the given position, relative to the local surface coordinates of + the parent surface. There are no guarantees as to what menu items + the window menu contains. + + This request must be used in response to some sort of user action + like a button press, key press, or touch down event. + + + + + + + + + + Start an interactive, user-driven move of the surface. + + This request must be used in response to some sort of user action + like a button press, key press, or touch down event. The passed + serial is used to determine the type of interactive move (touch, + pointer, etc). + + The server may ignore move requests depending on the state of + the surface (e.g. fullscreen or maximized), or if the passed serial + is no longer valid. + + If triggered, the surface will lose the focus of the device + (wl_pointer, wl_touch, etc) used for the move. It is up to the + compositor to visually indicate that the move is taking place, such as + updating a pointer cursor, during the move. There is no guarantee + that the device focus will return when the move is completed. + + + + + + + + These values are used to indicate which edge of a surface + is being dragged in a resize operation. + + + + + + + + + + + + + + + Start a user-driven, interactive resize of the surface. + + This request must be used in response to some sort of user action + like a button press, key press, or touch down event. The passed + serial is used to determine the type of interactive resize (touch, + pointer, etc). + + The server may ignore resize requests depending on the state of + the surface (e.g. fullscreen or maximized). + + If triggered, the client will receive configure events with the + "resize" state enum value and the expected sizes. See the "resize" + enum value for more details about what is required. The client + must also acknowledge configure events using "ack_configure". After + the resize is completed, the client will receive another "configure" + event without the resize state. + + If triggered, the surface also will lose the focus of the device + (wl_pointer, wl_touch, etc) used for the resize. It is up to the + compositor to visually indicate that the resize is taking place, + such as updating a pointer cursor, during the resize. There is no + guarantee that the device focus will return when the resize is + completed. + + The edges parameter specifies how the surface should be resized, + and is one of the values of the resize_edge enum. The compositor + may use this information to update the surface position for + example when dragging the top left corner. The compositor may also + use this information to adapt its behavior, e.g. choose an + appropriate cursor image. + + + + + + + + + The different state values used on the surface. This is designed for + state values like maximized, fullscreen. It is paired with the + configure event to ensure that both the client and the compositor + setting the state can be synchronized. + + States set in this way are double-buffered. They will get applied on + the next commit. + + + + The surface is maximized. 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 must be obeyed by the client. + + + + + The surface is being resized. The window geometry specified in the + configure event is a maximum; the client cannot resize beyond it. + Clients that have aspect ratio or cell sizing configuration can use + a smaller size, however. + + + + + Client window decorations should be painted as if the window is + active. Do not assume this means that the window actually has + keyboard or pointer focus. + + + + + + + Set a maximum size for the window. + + The client can specify a maximum size so that the compositor does + not try to configure the window beyond this size. + + The width and height arguments are in window geometry coordinates. + See xdg_surface.set_window_geometry. + + Values set in this way are double-buffered. They will get applied + on the next commit. + + The compositor can use this information to allow or disallow + different states like maximize or fullscreen and draw accurate + animations. + + Similarly, a tiling window manager may use this information to + place and resize client windows in a more effective way. + + The client should not rely on the compositor to obey the maximum + size. The compositor may decide to ignore the values set by the + client and request a larger size. + + If never set, or a value of zero in the request, means that the + client has no expected maximum size in the given dimension. + As a result, a client wishing to reset the maximum size + to an unspecified state can use zero for width and height in the + request. + + Requesting a maximum size to be smaller than the minimum size of + a surface is illegal and will result in a protocol error. + + The width and height must be greater than or equal to zero. Using + strictly negative values for width and height will result in a + protocol error. + + + + + + + + Set a minimum size for the window. + + The client can specify a minimum size so that the compositor does + not try to configure the window below this size. + + The width and height arguments are in window geometry coordinates. + See xdg_surface.set_window_geometry. + + Values set in this way are double-buffered. They will get applied + on the next commit. + + The compositor can use this information to allow or disallow + different states like maximize or fullscreen and draw accurate + animations. + + Similarly, a tiling window manager may use this information to + place and resize client windows in a more effective way. + + The client should not rely on the compositor to obey the minimum + size. The compositor may decide to ignore the values set by the + client and request a smaller size. + + If never set, or a value of zero in the request, means that the + client has no expected minimum size in the given dimension. + As a result, a client wishing to reset the minimum size + to an unspecified state can use zero for width and height in the + request. + + Requesting a minimum size to be larger than the maximum size of + a surface is illegal and will result in a protocol error. + + The width and height must be greater than or equal to zero. Using + strictly negative values for width and height will result in a + protocol error. + + + + + + + + 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). + + 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 + be used. + + If the surface was already maximized, the compositor will still emit + a configure event with the "maximized" state. + + + + + + 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). + + It is up to the compositor to position the surface after it was + unmaximized; usually the position the surface had before maximizing, if + applicable. + + If the surface was already not maximized, the compositor will still + emit a configure event without the "maximized" state. + + + + + + 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. + + 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. + + + + + + + + Request that the compositor minimize your surface. There is no + way to know if the surface is currently minimized, nor is there + any way to unset minimization on this surface. + + If you are looking to throttle redrawing when minimized, please + instead use the wl_surface.frame event for this, as this will + also work with live previews on windows in Alt-Tab, Expose or + similar compositor features. + + + + + + This configure event asks the client to resize its toplevel surface or + to change its state. The configured state should not be applied + immediately. See xdg_surface.configure for details. + + The width and height arguments specify a hint to the window + about how its surface should be resized in window geometry + coordinates. See set_window_geometry. + + If the width or height arguments are zero, it means the client + should decide its own window dimension. This may happen when the + compositor needs to configure the state of the surface but doesn't + have any information about any previous or expected dimension. + + The states listed in the event specify how the width/height + arguments should be interpreted, and possibly how it should be + drawn. + + Clients must send an ack_configure in response to this event. See + xdg_surface.configure and xdg_surface.ack_configure for details. + + + + + + + + + The close event is sent by the compositor when the user + wants the surface to be closed. This should be equivalent to + the user clicking the close button in client-side decorations, + if your application has any. + + This is only a request that the user intends to close the + window. The client may choose to ignore this request, or show + a dialog to ask the user to save their data, etc. + + + + + + + A popup surface is a short-lived, temporary surface. It can be used to + implement for example menus, popovers, tooltips and other similar user + interface concepts. + + A popup can be made to take an explicit grab. See xdg_popup.grab for + details. + + When the popup is dismissed, a popup_done event will be sent out, and at + the same time the surface will be unmapped. See the xdg_popup.popup_done + event for details. + + Explicitly destroying the xdg_popup object will also dismiss the popup and + unmap the surface. Clients that want to dismiss the popup when another + 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. + + + + + + + + + This destroys the popup. Explicitly destroying the xdg_popup + object will also dismiss the popup, and unmap the surface. + + If this xdg_popup is not the "topmost" popup, a protocol error + will be sent. + + + + + + This request makes the created popup take an explicit grab. An explicit + grab will be dismissed when the user dismisses the popup, or when the + client destroys the xdg_popup. This can be done by the user clicking + outside the surface, using the keyboard, or even locking the screen + through closing the lid or a timeout. + + If the compositor denies the grab, the popup will be immediately + dismissed. + + This request must be used in response to some sort of user action like a + button press, key press, or touch down event. The serial number of the + event should be passed as 'serial'. + + The parent of a grabbing popup must either be an xdg_toplevel surface or + another xdg_popup with an explicit grab. If the parent is another + xdg_popup it means that the popups are nested, with this popup now being + the topmost popup. + + Nested popups must be destroyed in the reverse order they were created + in, e.g. the only popup you are allowed to destroy at all times is the + topmost one. + + When compositors choose to dismiss a popup, they may dismiss every + nested grabbing popup as well. When a compositor dismisses popups, it + will follow the same dismissing order as required from the client. + + The parent of a grabbing popup must either be another xdg_popup with an + active explicit grab, or an xdg_popup or xdg_toplevel, if there are no + explicit grabs already taken. + + If the topmost grabbing popup is destroyed, the grab will be returned to + the parent of the popup, if that parent previously had an explicit grab. + + If the parent is a grabbing popup which has already been dismissed, this + popup will be immediately dismissed. If the parent is a popup that did + not take an explicit grab, an error will be raised. + + During a popup grab, the client owning the grab will receive pointer + and touch events for all their surfaces as normal (similar to an + "owner-events" grab in X11 parlance), while the top most grabbing popup + will always have keyboard focus. + + + + + + + + This event asks the popup surface to configure itself given the + configuration. The configured state should not be applied immediately. + See xdg_surface.configure for details. + + 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. + + + + + + + + + + The popup_done event is sent out when a popup is dismissed by the + compositor. The client should destroy the xdg_popup object at this + point. + + + + + diff --git a/sommelier/sommelier-compositor.c b/sommelier/sommelier-compositor.c new file mode 100644 index 0000000..86b304e --- /dev/null +++ b/sommelier/sommelier-compositor.c @@ -0,0 +1,894 @@ +// 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_region { + struct sl_context* ctx; + struct wl_resource* resource; + struct wl_region* proxy; +}; + +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-data-device-manager.c b/sommelier/sommelier-data-device-manager.c new file mode 100644 index 0000000..4693735 --- /dev/null +++ b/sommelier/sommelier-data-device-manager.c @@ -0,0 +1,580 @@ +// 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 + +struct sl_host_data_device_manager { + struct sl_context* ctx; + struct wl_resource* resource; + struct wl_data_device_manager* proxy; +}; + +struct sl_host_data_device { + struct sl_context* ctx; + struct wl_resource* resource; + struct wl_data_device* proxy; +}; + +struct sl_host_data_source { + struct wl_resource* resource; + struct wl_data_source* proxy; +}; + +struct sl_host_data_offer { + struct sl_context* ctx; + struct wl_resource* resource; + struct wl_data_offer* proxy; +}; + +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; +}; + +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); +} + +static int sl_handle_data_transfer_read(int fd, uint32_t mask, void* data) { + struct sl_data_transfer* transfer = (struct sl_data_transfer*)data; + if ((mask & WL_EVENT_READABLE) == 0) { + assert(mask & (WL_EVENT_HANGUP | WL_EVENT_ERROR)); + + // Epoll (and therefore wl_event_loop) will notify listeners of errors and + // hangups even if all other events are disabled. Therefore, at this point, + // we don't know whether we didn't get a readable event because the fd has + // been exhausted, or because we aren't in the reading state and weren't + // listening for one. We can make the distinction be checking if there's any + // data left in the buffer. If there is, then we are just waiting for the + // writing to finish, otherwise end the transfer. + + // In the case of an error, where there is not likely to be any more data to + // read, we still want to wait for any data we did get to be written out. + if (!transfer->bytes_left) { + sl_data_transfer_destroy(transfer); + } + return 0; + } + + // At this point we must be in the reading state. + assert(!transfer->bytes_left); + + transfer->bytes_left = + read(transfer->read_fd, transfer->data, sizeof(transfer->data)); + if (transfer->bytes_left > 0) { + 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); + } else { + // On a read error or EOF, end the transfer. + sl_data_transfer_destroy(transfer); + } + + return 0; +} + +static int sl_handle_data_transfer_write(int fd, uint32_t mask, void* data) { + struct sl_data_transfer* transfer = (struct sl_data_transfer*)data; + int rv; + + // If we receive a HANGUP or ERROR event on the write source then there is no + // point in continuing the transfer. We could still read more data, but we + // couldn't send it to the recipient, so just destroy the transfer now. + if ((mask & WL_EVENT_WRITABLE) == 0) { + assert(mask & (WL_EVENT_HANGUP | WL_EVENT_ERROR)); + sl_data_transfer_destroy(transfer); + return 0; + } + + // At this point we must be in the writing state. + assert(transfer->bytes_left); + + rv = write(transfer->write_fd, transfer->data + transfer->offset, + transfer->bytes_left); + + if (rv < 0) { + // On a write error, end the transfer. + sl_data_transfer_destroy(transfer); + } else { + assert(rv <= 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); + } + + // If there is still data left, continue in the writing state. + return 0; +} + +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; + + flags = fcntl(write_fd, F_GETFL, 0); + rv = fcntl(write_fd, F_SETFL, flags | O_NONBLOCK); + assert(!rv); + UNUSED(rv); + + // Start out the transfer in the reading state. + transfer = malloc(sizeof(*transfer)); + assert(transfer); + transfer->read_fd = read_fd; + transfer->write_fd = write_fd; + transfer->offset = 0; + transfer->bytes_left = 0; + transfer->read_event_source = + 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); +} + +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); + + 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; + } +} + +static void sl_data_offer_destroy(struct wl_client* client, + struct wl_resource* resource) { + 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}; + +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); + + wl_data_offer_send_offer(host->resource, mime_type); +} + +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); + + wl_data_offer_send_source_actions(host->resource, source_actions); +} + +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); + + wl_data_offer_send_action(host->resource, dnd_action); +} + +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); + + 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); +} + +static void sl_data_source_destroy(struct wl_client* client, + struct wl_resource* resource) { + 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}; + +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); + + wl_data_source_send_target(host->resource, mime_type); +} + +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); + + wl_data_source_send_send(host->resource, mime_type, fd); + close(fd); +} + +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); + + 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); + + 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); + + wl_data_source_send_dnd_finished(host->resource); +} + +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); + + wl_data_source_send_action(host->resource, dnd_action); +} + +static const struct wl_data_source_listener sl_data_source_listener = { + sl_data_source_target, sl_data_source_send, + sl_data_source_cancelled, sl_data_source_dnd_drop_performed, + 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); + + wl_data_source_destroy(host->proxy); + wl_resource_set_user_data(resource, NULL); + free(host); +} + +static void sl_data_device_start_drag(struct wl_client* client, + struct wl_resource* resource, + struct wl_resource* source_resource, + 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_source* host_source = + source_resource ? wl_resource_get_user_data(source_resource) : NULL; + struct sl_host_surface* host_origin = + origin_resource ? wl_resource_get_user_data(origin_resource) : NULL; + struct sl_host_surface* host_icon = + icon_resource ? 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); +} + +static void sl_data_device_release(struct wl_client* client, + struct wl_resource* resource) { + wl_resource_destroy(resource); +} + +static const struct wl_data_device_interface sl_data_device_implementation = { + sl_data_device_start_drag, sl_data_device_set_selection, + 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); + + host_data_offer->ctx = host->ctx; + host_data_offer->resource = wl_resource_create( + wl_resource_get_client(host->resource), &wl_data_offer_interface, + wl_resource_get_version(host->resource), 0); + wl_resource_set_implementation(host_data_offer->resource, + &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); + + wl_data_device_send_data_offer(host->resource, host_data_offer->resource); +} + +static void sl_data_device_enter(void* data, + struct wl_data_device* data_device, + uint32_t serial, + struct wl_surface* surface, + 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_offer* host_data_offer = + wl_data_offer_get_user_data(data_offer); + double scale = host->ctx->scale; + + 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); +} + +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); + + wl_data_device_send_leave(host->resource); +} + +static void sl_data_device_motion(void* data, + struct wl_data_device* data_device, + 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; + + 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)); +} + +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); + + 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); + + wl_data_device_send_selection(host->resource, host_data_offer->resource); +} + +static const struct wl_data_device_listener sl_data_device_listener = { + sl_data_device_data_offer, sl_data_device_enter, sl_data_device_leave, + 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); + + if (wl_data_device_get_version(host->proxy) >= + WL_DATA_DEVICE_RELEASE_SINCE_VERSION) { + wl_data_device_release(host->proxy); + } else { + wl_data_device_destroy(host->proxy); + } + wl_resource_set_user_data(resource, NULL); + free(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); + + host_data_source->resource = wl_resource_create( + client, &wl_data_source_interface, wl_resource_get_version(resource), id); + wl_resource_set_implementation(host_data_source->resource, + &sl_data_source_implementation, + 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); +} + +static void sl_data_device_manager_get_data_device( + struct wl_client* client, + struct wl_resource* resource, + 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); + + host_data_device->ctx = host->ctx; + 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, + &sl_data_device_implementation, + 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); +} + +static const struct wl_data_device_manager_interface + sl_data_device_manager_implementation = { + sl_data_device_manager_create_data_source, + sl_data_device_manager_get_data_device}; + +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); + + wl_data_device_manager_destroy(host->proxy); + wl_resource_set_user_data(resource, NULL); + free(host); +} + +static void sl_bind_host_data_device_manager(struct wl_client* client, + void* data, + 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); + host->ctx = ctx; + host->resource = + wl_resource_create(client, &wl_data_device_manager_interface, + MIN(version, ctx->data_device_manager->version), id); + wl_resource_set_implementation(host->resource, + &sl_data_device_manager_implementation, host, + sl_destroy_host_data_device_manager); + host->proxy = 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_set_user_data(host->proxy, host); +} + +struct sl_global* sl_data_device_manager_global_create(struct sl_context* ctx) { + return sl_global_create(ctx, &wl_data_device_manager_interface, + ctx->data_device_manager->version, ctx, + sl_bind_host_data_device_manager); +} diff --git a/sommelier/sommelier-display.c b/sommelier/sommelier-display.c new file mode 100644 index 0000000..e5ece4e --- /dev/null +++ b/sommelier/sommelier-display.c @@ -0,0 +1,126 @@ +// 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 + +static void sl_registry_bind(struct wl_client* client, + struct wl_resource* resource, + uint32_t name, + const char* interface, + uint32_t version, + uint32_t id) { + struct sl_host_registry* host = wl_resource_get_user_data(resource); + struct sl_global* global; + + wl_list_for_each(global, &host->ctx->globals, link) { + if (global->name == name) + break; + } + + assert(&global->link != &host->ctx->globals); + assert(version != 0); + assert(global->version >= version); + + global->bind(client, global->data, version, id); +} + +static const struct wl_registry_interface sl_registry_implementation = { + sl_registry_bind}; + +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); + + wl_callback_send_done(host->resource, serial); + wl_resource_destroy(host->resource); +} + +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); + + wl_callback_destroy(host->proxy); + wl_resource_set_user_data(resource, NULL); + free(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); + + 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); + + wl_list_remove(&host->link); + free(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_global* global; + + host_registry = malloc(sizeof(*host_registry)); + assert(host_registry); + + host_registry->ctx = ctx; + host_registry->resource = + wl_resource_create(client, &wl_registry_interface, 1, id); + wl_list_insert(&ctx->registries, &host_registry->link); + wl_resource_set_implementation(host_registry->resource, + &sl_registry_implementation, host_registry, + 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); + } +} + +static const struct wl_display_interface sl_display_implementation = { + sl_display_sync, sl_display_get_registry}; + +static enum wl_iterator_result sl_set_implementation( + struct wl_resource* resource, void* user_data) { + struct sl_context* ctx = (struct sl_context*)user_data; + + if (strcmp(wl_resource_get_class(resource), "wl_display") == 0) { + wl_resource_set_implementation(resource, &sl_display_implementation, ctx, + NULL); + return WL_ITERATOR_STOP; + } + + return WL_ITERATOR_CONTINUE; +} + +void sl_set_display_implementation(struct sl_context* ctx) { + // Find display resource and set implementation. + wl_client_for_each_resource(ctx->client, sl_set_implementation, ctx); +} diff --git a/sommelier/sommelier-drm.c b/sommelier/sommelier-drm.c new file mode 100644 index 0000000..8b2cc57 --- /dev/null +++ b/sommelier/sommelier-drm.c @@ -0,0 +1,259 @@ +// 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 "virtgpu_drm.h" + +#include "drm-server-protocol.h" +#include "linux-dmabuf-unstable-v1-client-protocol.h" + +struct sl_host_drm { + struct sl_context* ctx; + uint32_t version; + struct wl_resource* resource; + struct zwp_linux_dmabuf_v1* linux_dmabuf_proxy; + struct wl_callback* callback; +}; + +static void sl_drm_authenticate(struct wl_client* client, + struct wl_resource* resource, + uint32_t id) { + wl_drm_send_authenticated(resource); +} + +static void sl_drm_create_buffer(struct wl_client* client, + struct wl_resource* resource, + uint32_t id, + uint32_t name, + int32_t width, + int32_t height, + uint32_t stride, + uint32_t format) { + assert(0); +} + +static void sl_drm_create_planar_buffer(struct wl_client* client, + struct wl_resource* resource, + uint32_t id, + uint32_t name, + int32_t width, + int32_t height, + uint32_t format, + int32_t offset0, + int32_t stride0, + int32_t offset1, + int32_t stride1, + int32_t offset2, + int32_t stride2) { + assert(0); +} + +static void sl_drm_sync(struct sl_context *ctx, + struct sl_sync_point* sync_point) +{ + int drm_fd = gbm_device_get_fd(ctx->gbm); + struct drm_prime_handle prime_handle; + int ret; + + // First imports the prime fd to a gem handle. This will fail if this + // function was not passed a prime handle that can be imported by the drm + // device given to sommelier. + memset(&prime_handle, 0, sizeof(prime_handle)); + prime_handle.fd = sync_point->fd; + ret = drmIoctl(drm_fd, DRM_IOCTL_PRIME_FD_TO_HANDLE, &prime_handle); + if (!ret) { + struct drm_virtgpu_3d_wait wait_arg; + struct drm_gem_close gem_close; + + // Then attempts to wait for GPU operations to complete. This will fail + // silently if the drm device passed to sommelier is not a virtio-gpu + // device. + memset(&wait_arg, 0, sizeof(wait_arg)); + wait_arg.handle = prime_handle.handle; + drmIoctl(drm_fd, DRM_IOCTL_VIRTGPU_WAIT, &wait_arg); + + // Always close the handle we imported. + memset(&gem_close, 0, sizeof(gem_close)); + gem_close.handle = prime_handle.handle; + drmIoctl(drm_fd, DRM_IOCTL_GEM_CLOSE, &gem_close); + } +} + +static void sl_drm_create_prime_buffer(struct wl_client* client, + struct wl_resource* resource, + uint32_t id, + int32_t name, + int32_t width, + int32_t height, + uint32_t format, + int32_t offset0, + int32_t stride0, + int32_t offset1, + int32_t stride1, + int32_t offset2, + int32_t stride2) { + struct sl_host_drm* host = wl_resource_get_user_data(resource); + struct zwp_linux_buffer_params_v1* buffer_params; + + assert(name >= 0); + assert(!offset1); + assert(!stride1); + assert(!offset2); + assert(!stride2); + + // Attempts to correct stride0 with virtio-gpu specific resource information, + // if available. Ideally mesa/gbm should have the correct stride. Remove + // after crbug.com/892242 is resolved in mesa. + int is_gpu_buffer = 0; + if (host->ctx->gbm) { + int drm_fd = gbm_device_get_fd(host->ctx->gbm); + struct drm_prime_handle prime_handle; + int ret; + + // First imports the prime fd to a gem handle. This will fail if this + // function was not passed a prime handle that can be imported by the drm + // device given to sommelier. + memset(&prime_handle, 0, sizeof(prime_handle)); + 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_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); + // Correct stride0 if we are able to get proper resource info. + if (!ret) { + stride0 = info_arg.stride; + is_gpu_buffer = 1; + } + + // Always close the handle we imported. + memset(&gem_close, 0, sizeof(gem_close)); + gem_close.handle = prime_handle.handle; + drmIoctl(drm_fd, DRM_IOCTL_GEM_CLOSE, &gem_close); + } + } + + 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); + + struct sl_host_buffer* host_buffer = + sl_create_host_buffer(client, id, + zwp_linux_buffer_params_v1_create_immed( + buffer_params, width, height, format, 0), + width, height); + if (is_gpu_buffer) { + host_buffer->sync_point = sl_sync_point_create(name); + host_buffer->sync_point->sync = sl_drm_sync; + } else { + close(name); + } + + zwp_linux_buffer_params_v1_destroy(buffer_params); +} + +static const struct wl_drm_interface sl_drm_implementation = { + sl_drm_authenticate, sl_drm_create_buffer, sl_drm_create_planar_buffer, + 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); + + zwp_linux_dmabuf_v1_destroy(host->linux_dmabuf_proxy); + wl_callback_destroy(host->callback); + wl_resource_set_user_data(resource, NULL); + free(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); + + switch (format) { + case WL_DRM_FORMAT_RGB565: + case WL_DRM_FORMAT_ARGB8888: + case WL_DRM_FORMAT_ABGR8888: + case WL_DRM_FORMAT_XRGB8888: + case WL_DRM_FORMAT_XBGR8888: + wl_drm_send_format(host->resource, format); + default: + break; + } +} + +static void sl_drm_modifier(void* data, + struct zwp_linux_dmabuf_v1* linux_dmabuf, + uint32_t format, + uint32_t modifier_hi, + uint32_t modifier_lo) {} + +static const struct zwp_linux_dmabuf_v1_listener sl_linux_dmabuf_listener = { + sl_drm_format, sl_drm_modifier}; + +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); + + if (host->ctx->drm_device) + wl_drm_send_device(host->resource, host->ctx->drm_device); + if (host->version >= WL_DRM_CREATE_PRIME_BUFFER_SINCE_VERSION) + wl_drm_send_capabilities(host->resource, WL_DRM_CAPABILITY_PRIME); +} + +static const struct wl_callback_listener sl_drm_callback_listener = { + sl_drm_callback_done}; + +static void sl_bind_host_drm(struct wl_client* client, + void* data, + 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); + host->ctx = ctx; + host->version = MIN(version, 2); + host->resource = + wl_resource_create(client, &wl_drm_interface, host->version, id); + wl_resource_set_implementation(host->resource, &sl_drm_implementation, host, + sl_destroy_host_drm); + + host->linux_dmabuf_proxy = 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_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); +} + +struct sl_global* sl_drm_global_create(struct sl_context* ctx) { + assert(ctx->linux_dmabuf); + + // Early out if DMABuf protocol version is not sufficient. + if (ctx->linux_dmabuf->version < 2) + return NULL; + + return sl_global_create(ctx, &wl_drm_interface, 2, ctx, sl_bind_host_drm); +} diff --git a/sommelier/sommelier-gtk-shell.c b/sommelier/sommelier-gtk-shell.c new file mode 100644 index 0000000..ece2203 --- /dev/null +++ b/sommelier/sommelier-gtk-shell.c @@ -0,0 +1,162 @@ +// 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 "aura-shell-client-protocol.h" +#include "gtk-shell-server-protocol.h" + +struct sl_host_gtk_shell { + struct sl_aura_shell* aura_shell; + struct wl_resource* resource; + struct zaura_shell* proxy; + struct wl_callback* callback; + char* startup_id; + struct wl_list surfaces; +}; + +struct sl_host_gtk_surface { + struct wl_resource* resource; + struct zaura_surface* proxy; + struct wl_list link; + struct sl_aura_shell* aura_shell; +}; + +static void sl_gtk_surface_set_dbus_properties( + struct wl_client* client, + struct wl_resource* resource, + const char* application_id, + const char* app_menu_path, + const char* menubar_path, + 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); + + zaura_surface_set_application_id(host->proxy, application_id); +} + +static void sl_gtk_surface_set_modal(struct wl_client* client, + struct wl_resource* resource) {} + +static void sl_gtk_surface_unset_modal(struct wl_client* client, + struct wl_resource* resource) {} + +static void sl_gtk_surface_present(struct wl_client* client, + struct wl_resource* resource, + uint32_t time) {} + +static const struct gtk_surface1_interface sl_gtk_surface_implementation = { + sl_gtk_surface_set_dbus_properties, sl_gtk_surface_set_modal, + 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); + + zaura_surface_destroy(host->proxy); + wl_list_remove(&host->link); + wl_resource_set_user_data(resource, NULL); + free(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); + + wl_list_insert(&host->surfaces, &host_gtk_surface->link); + host_gtk_surface->aura_shell = host->aura_shell; + host_gtk_surface->resource = + wl_resource_create(client, >k_surface1_interface, 1, id); + wl_resource_set_implementation(host_gtk_surface->resource, + &sl_gtk_surface_implementation, + host_gtk_surface, sl_destroy_host_gtk_surface); + host_gtk_surface->proxy = + zaura_shell_get_aura_surface(host->proxy, host_surface->proxy); + zaura_surface_set_startup_id(host_gtk_surface->proxy, host->startup_id); +} + +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_surface* surface; + + free(host->startup_id); + host->startup_id = startup_id ? strdup(startup_id) : NULL; + + wl_list_for_each(surface, &host->surfaces, link) + zaura_surface_set_startup_id(surface->proxy, host->startup_id); +} + +static void sl_gtk_shell_system_bell(struct wl_client* client, + struct wl_resource* resource, + struct wl_resource* surface_resource) {} + +static const struct gtk_shell1_interface sl_gtk_shell_implementation = { + sl_gtk_shell_get_gtk_surface, sl_gtk_shell_set_startup_id, + 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); + + free(host->startup_id); + wl_callback_destroy(host->callback); + zaura_shell_destroy(host->proxy); + wl_resource_set_user_data(resource, NULL); + free(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); + + gtk_shell1_send_capabilities(host->resource, 0); +} + +static const struct wl_callback_listener sl_gtk_shell_callback_listener = { + sl_gtk_shell_callback_done}; + +static void sl_bind_host_gtk_shell(struct wl_client* client, + void* data, + 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); + 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); + 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); +} + +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-output.c b/sommelier/sommelier-output.c new file mode 100644 index 0000000..bd56c2d --- /dev/null +++ b/sommelier/sommelier-output.c @@ -0,0 +1,377 @@ +// 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 + +// The ergonomic advice for monitor distance is 50-75cm away, with laptops +// expected to be closer. This magic number is designed to correct that for the +// purpose of calculating a "useful" DPI. +// +// TODO(crbug.com/988325) Fix sommelier's scaling logic s.t. this ratio is +// unnecessary. +#define LAPTOP_TO_DESKTOP_DISTANCE_RATIO (2.0 / 3.0) + +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; +} + +void sl_output_get_host_output_state(struct sl_host_output* host, + int* scale, + int* physical_width, + int* physical_height, + int* width, + int* height) { + double preferred_scale = + sl_output_aura_scale_factor_to_double(host->preferred_scale); + double current_scale = + sl_output_aura_scale_factor_to_double(host->current_scale); + // "Ideal" means the scale factor you would need in order to make a pixel in + // the buffer map 1:1 with a physical pixel. In the absence of any better + // information, we assume a device whose display density maps faithfully to + // true pixels (i.e. 1.0). + double ideal_scale_factor = 1.0; + double scale_factor = host->scale_factor; + + // Use the scale factor we received from aura shell protocol when available. + if (host->ctx->aura_shell) { + double device_scale_factor = + sl_output_aura_scale_factor_to_double(host->device_scale_factor); + + ideal_scale_factor = device_scale_factor * preferred_scale; + scale_factor = device_scale_factor * current_scale; + } + + // Always use scale=1 and adjust geometry and mode based on ideal + // scale factor for Xwayland client. For other clients, pick an optimal + // scale and adjust geometry and mode based on it. + if (host->ctx->xwayland) { + if (scale) + *scale = 1; + *physical_width = host->physical_width * ideal_scale_factor / scale_factor; + *physical_height = + host->physical_height * ideal_scale_factor / scale_factor; + *width = host->width * host->ctx->scale / scale_factor; + *height = host->height * host->ctx->scale / scale_factor; + + // Historically, X applications use DPI to decide their scale (which is not + // ideal). The main problem is that in order to facilitate this, many X + // utilities lie about the DPI of the device in order to achieve the desired + // scaling, e.g. most laptops report a dpi of 96 even if that is inaccurate. + // + // The reason they have to lie is because laptop screens are typically + // closer to your eye than desktop monitors (by a factor of roughly 2/3), + // meaning they have to have proportionally higher DPI in order to "look" as + // high-def as the monitor. + // + // Since sommelier is in the business of lying about the screen's + // dimensions, we will also lie a bit more when we are dealing with the + // internal display, to make its dpi scale like a desktop monitor's would. + if (host->internal) { + *physical_width /= LAPTOP_TO_DESKTOP_DISTANCE_RATIO; + *physical_height /= LAPTOP_TO_DESKTOP_DISTANCE_RATIO; + } + } else { + int s = MIN(ceil(scale_factor / 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 / scale_factor; + *height = host->height * host->ctx->scale * s / scale_factor; + } + + if (host->ctx->dpi.size) { + int dpi = (*width * INCH_IN_MM) / *physical_width; + int adjusted_dpi = *((int*)host->ctx->dpi.data); + double mmpd; + int* p; + + // Choose the DPI bucket which is closest to the apparent DPI which we + // calculated above. + wl_array_for_each(p, &host->ctx->dpi) { + if (abs(*p - dpi) < abs(adjusted_dpi - dpi)) + adjusted_dpi = *p; + } + + mmpd = INCH_IN_MM / adjusted_dpi; + *physical_width = *width * mmpd + 0.5; + *physical_height = *height * mmpd + 0.5; + } +} + +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); + + switch (scale) { + case ZAURA_OUTPUT_SCALE_FACTOR_0400: + case ZAURA_OUTPUT_SCALE_FACTOR_0500: + case ZAURA_OUTPUT_SCALE_FACTOR_0550: + case ZAURA_OUTPUT_SCALE_FACTOR_0600: + case ZAURA_OUTPUT_SCALE_FACTOR_0625: + case ZAURA_OUTPUT_SCALE_FACTOR_0650: + case ZAURA_OUTPUT_SCALE_FACTOR_0700: + case ZAURA_OUTPUT_SCALE_FACTOR_0750: + case ZAURA_OUTPUT_SCALE_FACTOR_0800: + case ZAURA_OUTPUT_SCALE_FACTOR_0850: + case ZAURA_OUTPUT_SCALE_FACTOR_0900: + case ZAURA_OUTPUT_SCALE_FACTOR_0950: + case ZAURA_OUTPUT_SCALE_FACTOR_1000: + case ZAURA_OUTPUT_SCALE_FACTOR_1050: + case ZAURA_OUTPUT_SCALE_FACTOR_1100: + case ZAURA_OUTPUT_SCALE_FACTOR_1150: + case ZAURA_OUTPUT_SCALE_FACTOR_1125: + case ZAURA_OUTPUT_SCALE_FACTOR_1200: + case ZAURA_OUTPUT_SCALE_FACTOR_1250: + case ZAURA_OUTPUT_SCALE_FACTOR_1300: + case ZAURA_OUTPUT_SCALE_FACTOR_1400: + case ZAURA_OUTPUT_SCALE_FACTOR_1450: + case ZAURA_OUTPUT_SCALE_FACTOR_1500: + case ZAURA_OUTPUT_SCALE_FACTOR_1600: + case ZAURA_OUTPUT_SCALE_FACTOR_1750: + case ZAURA_OUTPUT_SCALE_FACTOR_1800: + case ZAURA_OUTPUT_SCALE_FACTOR_2000: + case ZAURA_OUTPUT_SCALE_FACTOR_2200: + case ZAURA_OUTPUT_SCALE_FACTOR_2250: + case ZAURA_OUTPUT_SCALE_FACTOR_2500: + case ZAURA_OUTPUT_SCALE_FACTOR_2750: + case ZAURA_OUTPUT_SCALE_FACTOR_3000: + case ZAURA_OUTPUT_SCALE_FACTOR_3500: + case ZAURA_OUTPUT_SCALE_FACTOR_4000: + case ZAURA_OUTPUT_SCALE_FACTOR_4500: + case ZAURA_OUTPUT_SCALE_FACTOR_5000: + break; + default: + fprintf(stderr, "warning: unknown scale factor: %d\n", scale); + break; + } + + 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-relative-pointer-manager.c b/sommelier/sommelier-relative-pointer-manager.c new file mode 100644 index 0000000..d1a80c2 --- /dev/null +++ b/sommelier/sommelier-relative-pointer-manager.c @@ -0,0 +1,146 @@ +// Copyright 2019 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 "relative-pointer-unstable-v1-server-protocol.h" +#include "relative-pointer-unstable-v1-client-protocol.h" + +struct sl_host_relative_pointer_manager { + struct sl_context* ctx; + struct wl_resource* resource; + struct zwp_relative_pointer_manager_v1* proxy; +}; + +struct sl_host_relative_pointer { + struct sl_context* ctx; + struct wl_resource* resource; + struct zwp_relative_pointer_v1* proxy; +}; + +static void sl_relative_pointer_relative_motion( + void* data, + struct zwp_relative_pointer_v1* relative_pointer, + uint32_t utime_hi, + uint32_t utime_lo, + wl_fixed_t dx, + wl_fixed_t dy, + 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); + + 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); + + zwp_relative_pointer_v1_destroy(host->proxy); + wl_resource_set_user_data(resource, NULL); + free(host); +} + +static void sl_relative_pointer_destroy(struct wl_client* client, + struct wl_resource* resource) { + wl_resource_destroy(resource); +} + +static struct zwp_relative_pointer_v1_listener sl_relative_pointer_listener = { + sl_relative_pointer_relative_motion, +}; + +static struct zwp_relative_pointer_v1_interface + sl_relative_pointer_implementation = { + sl_relative_pointer_destroy, +}; + +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); + + zwp_relative_pointer_manager_v1_destroy(host->proxy); + wl_resource_set_user_data(resource, NULL); + free(host); +} + +static void sl_relative_pointer_manager_destroy(struct wl_client* client, + struct wl_resource* resource) { + wl_resource_destroy(resource); +} + +static void sl_relative_pointer_manager_get_relative_pointer( + struct wl_client* client, + struct wl_resource* resource, + 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); + 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); + 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); + 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); +} + +static struct zwp_relative_pointer_manager_v1_interface + sl_relative_pointer_manager_implementation = { + sl_relative_pointer_manager_destroy, + sl_relative_pointer_manager_get_relative_pointer, +}; + +static void sl_bind_host_relative_pointer_manager(struct wl_client* client, + void* data, + uint32_t version, + uint32_t id) { + 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); + 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)); + zwp_relative_pointer_manager_v1_set_user_data(host->proxy, host); +} + +struct sl_global* sl_relative_pointer_manager_global_create( + struct sl_context* ctx) { + return sl_global_create(ctx, &zwp_relative_pointer_manager_v1_interface, 1, + ctx, sl_bind_host_relative_pointer_manager); +} diff --git a/sommelier/sommelier-seat.c b/sommelier/sommelier-seat.c new file mode 100644 index 0000000..7e4d4d2 --- /dev/null +++ b/sommelier/sommelier-seat.c @@ -0,0 +1,778 @@ +// 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 "keyboard-extension-unstable-v1-client-protocol.h" + +struct sl_host_keyboard { + struct sl_seat* seat; + struct wl_resource* resource; + struct wl_keyboard* proxy; + struct zcr_extended_keyboard_v1* extended_keyboard_proxy; + struct wl_resource* focus_resource; + struct wl_listener focus_resource_listener; + uint32_t focus_serial; + struct xkb_keymap* keymap; + struct xkb_state* state; + xkb_mod_mask_t control_mask; + xkb_mod_mask_t alt_mask; + xkb_mod_mask_t shift_mask; + uint32_t modifiers; + struct wl_array pressed_keys; +}; + +struct sl_host_touch { + struct sl_seat* seat; + struct wl_resource* resource; + struct wl_touch* proxy; + struct wl_resource* focus_resource; + struct wl_listener focus_resource_listener; +}; + +static void sl_host_pointer_set_cursor(struct wl_client* client, + struct wl_resource* resource, + uint32_t serial, + 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_surface* host_surface = NULL; + double scale = host->seat->ctx->scale; + + if (surface_resource) { + host_surface = 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); + } + + wl_pointer_set_cursor(host->proxy, serial, + host_surface ? host_surface->proxy : NULL, + hotspot_x / scale, hotspot_y / scale); +} + +static void sl_host_pointer_release(struct wl_client* client, + struct wl_resource* resource) { + wl_resource_destroy(resource); +} + +static const struct wl_pointer_interface sl_pointer_implementation = { + sl_host_pointer_set_cursor, sl_host_pointer_release}; + +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); + + host_surface->last_event_serial = serial; +} + +static void sl_pointer_set_focus(struct sl_host_pointer* host, + uint32_t serial, + struct sl_host_surface* host_surface, + wl_fixed_t x, + wl_fixed_t y) { + struct wl_resource* surface_resource = + host_surface ? host_surface->resource : NULL; + + if (surface_resource == host->focus_resource) + return; + + if (host->focus_resource) + wl_pointer_send_leave(host->resource, serial, host->focus_resource); + + wl_list_remove(&host->focus_resource_listener.link); + wl_list_init(&host->focus_resource_listener.link); + host->focus_resource = surface_resource; + host->focus_serial = serial; + + 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)); + sl_roundtrip(host->seat->ctx); + } + + wl_resource_add_destroy_listener(surface_resource, + &host->focus_resource_listener); + + wl_pointer_send_enter(host->resource, serial, surface_resource, x * scale, + y * scale); + } +} + +static void sl_pointer_enter(void* data, + struct wl_pointer* pointer, + uint32_t serial, + 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_surface* host_surface = + surface ? wl_surface_get_user_data(surface) : NULL; + + if (!host_surface) + return; + + sl_pointer_set_focus(host, serial, host_surface, x, y); + + if (host->focus_resource) + sl_set_last_event_serial(host->focus_resource, serial); + host->seat->last_serial = serial; +} + +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); + + sl_pointer_set_focus(host, serial, NULL, 0, 0); +} + +static void sl_pointer_motion(void* data, + struct wl_pointer* pointer, + 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; + + wl_pointer_send_motion(host->resource, time, x * scale, y * scale); +} + +static void sl_pointer_button(void* data, + struct wl_pointer* pointer, + uint32_t serial, + uint32_t time, + uint32_t button, + uint32_t state) { + struct sl_host_pointer* host = wl_pointer_get_user_data(pointer); + + wl_pointer_send_button(host->resource, serial, time, button, state); + + if (host->focus_resource) + sl_set_last_event_serial(host->focus_resource, serial); + host->seat->last_serial = serial; +} + +static void sl_pointer_axis(void* data, + struct wl_pointer* pointer, + 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; + + wl_pointer_send_axis(host->resource, time, axis, value * scale); +} + +static void sl_pointer_frame(void* data, struct wl_pointer* pointer) { + struct sl_host_pointer* host = wl_pointer_get_user_data(pointer); + + wl_pointer_send_frame(host->resource); +} + +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); + + wl_pointer_send_axis_source(host->resource, axis_source); +} + +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); + + wl_pointer_send_axis_stop(host->resource, time, axis); +} + +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); + + wl_pointer_send_axis_discrete(host->resource, axis, discrete); +} + +static const struct wl_pointer_listener sl_pointer_listener = { + sl_pointer_enter, sl_pointer_leave, sl_pointer_motion, + sl_pointer_button, sl_pointer_axis, sl_pointer_frame, + sl_pointer_axis_source, sl_pointer_axis_stop, sl_pointer_axis_discrete}; + +static void sl_host_keyboard_release(struct wl_client* client, + struct wl_resource* resource) { + wl_resource_destroy(resource); +} + +static const struct wl_keyboard_interface sl_keyboard_implementation = { + sl_host_keyboard_release}; + +static void sl_keyboard_keymap(void* data, + struct wl_keyboard* keyboard, + uint32_t format, + int32_t fd, + uint32_t size) { + struct sl_host_keyboard* host = wl_keyboard_get_user_data(keyboard); + + wl_keyboard_send_keymap(host->resource, format, fd, size); + + if (format == WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) { + void* data = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0); + + assert(data != MAP_FAILED); + + if (host->keymap) + xkb_keymap_unref(host->keymap); + + host->keymap = xkb_keymap_new_from_string( + host->seat->ctx->xkb_context, data, XKB_KEYMAP_FORMAT_TEXT_V1, 0); + assert(host->keymap); + + munmap(data, size); + + if (host->state) + xkb_state_unref(host->state); + host->state = xkb_state_new(host->keymap); + assert(host->state); + + host->control_mask = 1 << xkb_keymap_mod_get_index(host->keymap, "Control"); + host->alt_mask = 1 << xkb_keymap_mod_get_index(host->keymap, "Mod1"); + host->shift_mask = 1 << xkb_keymap_mod_get_index(host->keymap, "Shift"); + } + + close(fd); +} + +static void sl_keyboard_set_focus(struct sl_host_keyboard* host, + uint32_t serial, + struct sl_host_surface* host_surface, + struct wl_array* keys) { + struct wl_resource* surface_resource = + host_surface ? host_surface->resource : NULL; + + if (surface_resource == host->focus_resource) + return; + + if (host->focus_resource) + wl_keyboard_send_leave(host->resource, serial, host->focus_resource); + + wl_list_remove(&host->focus_resource_listener.link); + wl_list_init(&host->focus_resource_listener.link); + host->focus_resource = surface_resource; + host->focus_serial = serial; + + if (surface_resource) { + wl_resource_add_destroy_listener(surface_resource, + &host->focus_resource_listener); + wl_keyboard_send_enter(host->resource, serial, surface_resource, keys); + } + + host->seat->last_serial = serial; +} + +static void sl_keyboard_enter(void* data, + struct wl_keyboard* keyboard, + 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_surface* host_surface = + surface ? wl_surface_get_user_data(surface) : NULL; + + if (!host_surface) + return; + + wl_array_copy(&host->pressed_keys, keys); + sl_keyboard_set_focus(host, serial, host_surface, keys); + + host->seat->last_serial = serial; +} + +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 wl_array array; + + wl_array_init(&array); + sl_keyboard_set_focus(host, serial, NULL, &array); +} + +static int sl_array_set_add(struct wl_array* array, uint32_t key) { + uint32_t* k; + + wl_array_for_each(k, array) { + if (*k == key) + return 0; + } + k = wl_array_add(array, sizeof(key)); + assert(k); + *k = key; + return 1; +} + +static int sl_array_set_remove(struct wl_array* array, uint32_t key) { + uint32_t* k; + + wl_array_for_each(k, array) { + if (*k == key) { + uint32_t* end = (uint32_t*)((char*)array->data + array->size); + + *k = *(end - 1); + array->size -= sizeof(*k); + return 1; + } + } + return 0; +} + +static void sl_keyboard_key(void* data, + struct wl_keyboard* keyboard, + uint32_t serial, + uint32_t time, + uint32_t key, + uint32_t state) { + struct sl_host_keyboard* host = wl_keyboard_get_user_data(keyboard); + int handled = 1; + + if (state == WL_KEYBOARD_KEY_STATE_PRESSED) { + if (host->state) { + const xkb_keysym_t* symbols; + uint32_t num_symbols; + xkb_keysym_t symbol = XKB_KEY_NoSymbol; + uint32_t code = key + 8; + struct sl_accelerator* accelerator; + + num_symbols = xkb_state_key_get_syms(host->state, code, &symbols); + if (num_symbols == 1) + 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; + } + } + + // Forward key pressed event if it should be handled and not + // already pressed. + if (handled) { + if (sl_array_set_add(&host->pressed_keys, key)) + wl_keyboard_send_key(host->resource, serial, time, key, state); + } + } else { + // Forward key release event if currently pressed. + handled = sl_array_set_remove(&host->pressed_keys, key); + if (handled) + wl_keyboard_send_key(host->resource, serial, time, key, state); + } + + if (host->focus_resource) + sl_set_last_event_serial(host->focus_resource, serial); + host->seat->last_serial = serial; + + if (host->extended_keyboard_proxy) { + zcr_extended_keyboard_v1_ack_key( + host->extended_keyboard_proxy, serial, + handled ? ZCR_EXTENDED_KEYBOARD_V1_HANDLED_STATE_HANDLED + : ZCR_EXTENDED_KEYBOARD_V1_HANDLED_STATE_NOT_HANDLED); + } +} + +static void sl_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) { + struct sl_host_keyboard* host = wl_keyboard_get_user_data(keyboard); + xkb_mod_mask_t mask; + + wl_keyboard_send_modifiers(host->resource, serial, mods_depressed, + mods_latched, mods_locked, group); + + if (host->focus_resource) + sl_set_last_event_serial(host->focus_resource, serial); + host->seat->last_serial = serial; + + if (!host->keymap) + return; + + 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->modifiers = 0; + if (mask & host->control_mask) + host->modifiers |= CONTROL_MASK; + if (mask & host->alt_mask) + host->modifiers |= ALT_MASK; + if (mask & host->shift_mask) + host->modifiers |= SHIFT_MASK; +} + +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); + + wl_keyboard_send_repeat_info(host->resource, rate, delay); +} + +static const struct wl_keyboard_listener sl_keyboard_listener = { + sl_keyboard_keymap, sl_keyboard_enter, sl_keyboard_leave, + sl_keyboard_key, sl_keyboard_modifiers, sl_keyboard_repeat_info}; + +static void sl_host_touch_release(struct wl_client* client, + struct wl_resource* resource) { + wl_resource_destroy(resource); +} + +static const struct wl_touch_interface sl_touch_implementation = { + sl_host_touch_release}; + +static void sl_host_touch_down(void* data, + struct wl_touch* touch, + uint32_t serial, + uint32_t time, + struct wl_surface* surface, + 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_surface* host_surface = + surface ? wl_surface_get_user_data(surface) : NULL; + double scale = host->seat->ctx->scale; + + if (!host_surface) + return; + + if (host_surface->resource != host->focus_resource) { + wl_list_remove(&host->focus_resource_listener.link); + wl_list_init(&host->focus_resource_listener.link); + host->focus_resource = host_surface->resource; + wl_resource_add_destroy_listener(host_surface->resource, + &host->focus_resource_listener); + } + + if (host->seat->ctx->xwayland) { + // Make sure focus surface is on top before sending down event. + sl_restack_windows(host->seat->ctx, + wl_resource_get_id(host_surface->resource)); + sl_roundtrip(host->seat->ctx); + } + + wl_touch_send_down(host->resource, serial, time, host_surface->resource, id, + x * scale, y * scale); + + if (host->focus_resource) + sl_set_last_event_serial(host->focus_resource, serial); + host->seat->last_serial = serial; +} + +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); + + wl_list_remove(&host->focus_resource_listener.link); + wl_list_init(&host->focus_resource_listener.link); + host->focus_resource = NULL; + + wl_touch_send_up(host->resource, serial, time, id); + + if (host->focus_resource) + sl_set_last_event_serial(host->focus_resource, serial); + host->seat->last_serial = serial; +} + +static void sl_host_touch_motion(void* data, + struct wl_touch* touch, + uint32_t time, + 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; + + wl_touch_send_motion(host->resource, time, id, x * scale, y * scale); +} + +static void sl_host_touch_frame(void* data, struct wl_touch* touch) { + struct sl_host_touch* host = 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); + + wl_touch_send_cancel(host->resource); +} + +static const struct wl_touch_listener sl_touch_listener = { + sl_host_touch_down, sl_host_touch_up, sl_host_touch_motion, + 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); + + if (wl_pointer_get_version(host->proxy) >= WL_POINTER_RELEASE_SINCE_VERSION) { + wl_pointer_release(host->proxy); + } else { + wl_pointer_destroy(host->proxy); + } + wl_list_remove(&host->focus_resource_listener.link); + wl_resource_set_user_data(resource, NULL); + free(host); +} + +static void sl_pointer_focus_resource_destroyed(struct wl_listener* listener, + void* data) { + struct sl_host_pointer* host; + + host = wl_container_of(listener, host, focus_resource_listener); + sl_pointer_set_focus(host, host->focus_serial, NULL, 0, 0); +} + +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); + + host_pointer->seat = host->seat; + host_pointer->resource = wl_resource_create( + client, &wl_pointer_interface, wl_resource_get_version(resource), id); + wl_resource_set_implementation(host_pointer->resource, + &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_serial = 0; +} + +static void sl_destroy_host_keyboard(struct wl_resource* resource) { + struct sl_host_keyboard* host = wl_resource_get_user_data(resource); + + if (host->extended_keyboard_proxy) + zcr_extended_keyboard_v1_destroy(host->extended_keyboard_proxy); + + wl_array_release(&host->pressed_keys); + if (host->keymap) + xkb_keymap_unref(host->keymap); + if (host->state) + xkb_state_unref(host->state); + + if (wl_keyboard_get_version(host->proxy) >= + WL_KEYBOARD_RELEASE_SINCE_VERSION) { + wl_keyboard_release(host->proxy); + } else { + wl_keyboard_destroy(host->proxy); + } + + wl_list_remove(&host->focus_resource_listener.link); + wl_resource_set_user_data(resource, NULL); + free(host); +} + +static void sl_keyboard_focus_resource_destroyed(struct wl_listener* listener, + void* data) { + struct sl_host_keyboard* host; + struct wl_array array; + + host = wl_container_of(listener, host, focus_resource_listener); + wl_array_init(&array); + sl_keyboard_set_focus(host, host->focus_serial, NULL, &array); +} + +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); + + host_keyboard->seat = host->seat; + host_keyboard->resource = wl_resource_create( + client, &wl_keyboard_interface, wl_resource_get_version(resource), id); + wl_resource_set_implementation(host_keyboard->resource, + &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); + host_keyboard->focus_resource_listener.notify = + sl_keyboard_focus_resource_destroyed; + host_keyboard->focus_resource = NULL; + host_keyboard->focus_serial = 0; + host_keyboard->keymap = NULL; + host_keyboard->state = NULL; + host_keyboard->control_mask = 0; + host_keyboard->alt_mask = 0; + host_keyboard->shift_mask = 0; + host_keyboard->modifiers = 0; + wl_array_init(&host_keyboard->pressed_keys); + + if (host->seat->ctx->keyboard_extension) { + host_keyboard->extended_keyboard_proxy = + zcr_keyboard_extension_v1_get_extended_keyboard( + host->seat->ctx->keyboard_extension->internal, + host_keyboard->proxy); + } else { + host_keyboard->extended_keyboard_proxy = NULL; + } +} + +static void sl_destroy_host_touch(struct wl_resource* resource) { + struct sl_host_touch* host = wl_resource_get_user_data(resource); + + if (wl_touch_get_version(host->proxy) >= WL_TOUCH_RELEASE_SINCE_VERSION) { + wl_touch_release(host->proxy); + } else { + wl_touch_destroy(host->proxy); + } + wl_resource_set_user_data(resource, NULL); + free(host); +} + +static void sl_touch_focus_resource_destroyed(struct wl_listener* listener, + void* data) { + struct sl_host_touch* host; + + host = wl_container_of(listener, host, focus_resource_listener); + wl_list_remove(&host->focus_resource_listener.link); + wl_list_init(&host->focus_resource_listener.link); + host->focus_resource = 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); + + host_touch->seat = host->seat; + host_touch->resource = wl_resource_create( + client, &wl_touch_interface, wl_resource_get_version(resource), id); + 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); +} + +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}; + +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); + + 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); + + if (wl_resource_get_version(host->resource) >= WL_SEAT_NAME_SINCE_VERSION) + wl_seat_send_name(host->resource, name); +} + +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); + + sl_host_seat_removed(host); + + if (wl_seat_get_version(host->proxy) >= WL_SEAT_RELEASE_SINCE_VERSION) + wl_seat_release(host->proxy); + else + wl_seat_destroy(host->proxy); + wl_resource_set_user_data(resource, NULL); + free(host); +} + +static void sl_bind_host_seat(struct wl_client* client, + void* data, + 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); + 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); + wl_seat_add_listener(host->proxy, &sl_seat_listener, host); + + sl_host_seat_added(host); +} + +struct sl_global* sl_seat_global_create(struct sl_seat* seat) { + return sl_global_create(seat->ctx, &wl_seat_interface, seat->version, seat, + sl_bind_host_seat); +} diff --git a/sommelier/sommelier-shell.c b/sommelier/sommelier-shell.c new file mode 100644 index 0000000..f316706 --- /dev/null +++ b/sommelier/sommelier-shell.c @@ -0,0 +1,234 @@ +// 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-shm.c b/sommelier/sommelier-shm.c new file mode 100644 index 0000000..1352aa8 --- /dev/null +++ b/sommelier/sommelier-shm.c @@ -0,0 +1,328 @@ +// 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 "drm-server-protocol.h" +#include "linux-dmabuf-unstable-v1-client-protocol.h" + +struct sl_host_shm_pool { + struct sl_shm* shm; + struct wl_resource* resource; + struct wl_shm_pool* proxy; + int fd; +}; + +struct sl_host_shm { + struct sl_shm* shm; + struct wl_resource* resource; + struct wl_shm* shm_proxy; + struct zwp_linux_dmabuf_v1* linux_dmabuf_proxy; +}; + +size_t sl_shm_bpp_for_shm_format(uint32_t format) { + switch (format) { + case WL_SHM_FORMAT_NV12: + return 1; + case WL_SHM_FORMAT_RGB565: + return 2; + case WL_SHM_FORMAT_ARGB8888: + case WL_SHM_FORMAT_ABGR8888: + case WL_SHM_FORMAT_XRGB8888: + case WL_SHM_FORMAT_XBGR8888: + return 4; + } + assert(0); + return 0; +} + +size_t sl_shm_num_planes_for_shm_format(uint32_t format) { + switch (format) { + case WL_SHM_FORMAT_NV12: + return 2; + case WL_SHM_FORMAT_RGB565: + case WL_SHM_FORMAT_ARGB8888: + case WL_SHM_FORMAT_ABGR8888: + case WL_SHM_FORMAT_XRGB8888: + case WL_SHM_FORMAT_XBGR8888: + return 1; + } + assert(0); + return 0; +} + +static size_t sl_y_subsampling_for_shm_format_plane(uint32_t format, + size_t plane) { + switch (format) { + case WL_SHM_FORMAT_NV12: { + const size_t subsampling[] = {1, 2}; + + assert(plane < ARRAY_SIZE(subsampling)); + return subsampling[plane]; + } + case WL_SHM_FORMAT_RGB565: + case WL_SHM_FORMAT_ARGB8888: + case WL_SHM_FORMAT_ABGR8888: + case WL_SHM_FORMAT_XRGB8888: + case WL_SHM_FORMAT_XBGR8888: + return 1; + } + assert(0); + return 0; +} + +static int sl_offset_for_shm_format_plane(uint32_t format, + size_t height, + size_t stride, + size_t plane) { + switch (format) { + case WL_SHM_FORMAT_NV12: { + const size_t offset[] = {0, 1}; + + assert(plane < ARRAY_SIZE(offset)); + return offset[plane] * height * stride; + } + case WL_SHM_FORMAT_RGB565: + case WL_SHM_FORMAT_ARGB8888: + case WL_SHM_FORMAT_ABGR8888: + case WL_SHM_FORMAT_XRGB8888: + case WL_SHM_FORMAT_XBGR8888: + return 0; + } + assert(0); + return 0; +} + +static size_t sl_size_for_shm_format_plane(uint32_t format, + size_t height, + size_t stride, + size_t plane) { + return height / sl_y_subsampling_for_shm_format_plane(format, plane) * stride; +} + +static size_t sl_size_for_shm_format(uint32_t format, + size_t height, + size_t stride) { + size_t i, num_planes = sl_shm_num_planes_for_shm_format(format); + size_t total_size = 0; + + for (i = 0; i < num_planes; ++i) { + size_t size = sl_size_for_shm_format_plane(format, height, stride, i); + size_t offset = sl_offset_for_shm_format_plane(format, height, stride, i); + total_size = MAX(total_size, size + offset); + } + + return total_size; +} + +static void sl_host_shm_pool_create_host_buffer(struct wl_client* client, + struct wl_resource* resource, + uint32_t id, + int32_t offset, + int32_t width, + int32_t height, + int32_t stride, + uint32_t format) { + struct sl_host_shm_pool* host = wl_resource_get_user_data(resource); + + if (host->shm->ctx->shm_driver == SHM_DRIVER_NOOP) { + assert(host->proxy); + sl_create_host_buffer(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; + } +} + +static void sl_host_shm_pool_destroy(struct wl_client* client, + struct wl_resource* resource) { + wl_resource_destroy(resource); +} + +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); + + if (host->proxy) + wl_shm_pool_resize(host->proxy, size); +} + +static const struct wl_shm_pool_interface sl_shm_pool_implementation = { + sl_host_shm_pool_create_host_buffer, sl_host_shm_pool_destroy, + 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); + + 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); +} + +static void sl_shm_create_host_pool(struct wl_client* client, + struct wl_resource* resource, + 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); + + host_shm_pool->shm = host->shm; + host_shm_pool->fd = -1; + host_shm_pool->proxy = NULL; + host_shm_pool->resource = + wl_resource_create(client, &wl_shm_pool_interface, 1, id); + wl_resource_set_implementation(host_shm_pool->resource, + &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; + } +} + +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); + + switch (format) { + case WL_SHM_FORMAT_RGB565: + case WL_SHM_FORMAT_ARGB8888: + case WL_SHM_FORMAT_ABGR8888: + case WL_SHM_FORMAT_XRGB8888: + case WL_SHM_FORMAT_XBGR8888: + wl_shm_send_format(host->resource, format); + default: + break; + } +} + +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); + + // Forward SHM versions of supported formats. + switch (format) { + case WL_DRM_FORMAT_NV12: + wl_shm_send_format(host->resource, WL_SHM_FORMAT_NV12); + break; + case WL_DRM_FORMAT_RGB565: + wl_shm_send_format(host->resource, WL_SHM_FORMAT_RGB565); + break; + case WL_DRM_FORMAT_ARGB8888: + wl_shm_send_format(host->resource, WL_SHM_FORMAT_ARGB8888); + break; + case WL_DRM_FORMAT_ABGR8888: + wl_shm_send_format(host->resource, WL_SHM_FORMAT_ABGR8888); + break; + case WL_DRM_FORMAT_XRGB8888: + wl_shm_send_format(host->resource, WL_SHM_FORMAT_XRGB8888); + break; + case WL_DRM_FORMAT_XBGR8888: + wl_shm_send_format(host->resource, WL_SHM_FORMAT_XBGR8888); + break; + } +} + +static void sl_drm_modifier(void* data, + struct zwp_linux_dmabuf_v1* linux_dmabuf, + uint32_t format, + uint32_t modifier_hi, + uint32_t modifier_lo) {} + +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); + + 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); +} + +static void sl_bind_host_shm(struct wl_client* client, + void* data, + 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); + host->shm = ctx->shm; + host->shm_proxy = NULL; + host->linux_dmabuf_proxy = NULL; + host->resource = wl_resource_create(client, &wl_shm_interface, 1, id); + 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; + } +} + +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/sommelier-subcompositor.c b/sommelier/sommelier-subcompositor.c new file mode 100644 index 0000000..2bc04e2 --- /dev/null +++ b/sommelier/sommelier-subcompositor.c @@ -0,0 +1,153 @@ +// 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_subcompositor { + struct sl_context* ctx; + struct wl_resource* resource; + struct wl_subcompositor* proxy; +}; + +struct sl_host_subsurface { + struct sl_context* ctx; + struct wl_resource* resource; + struct wl_subsurface* proxy; +}; + +static void sl_subsurface_destroy(struct wl_client* client, + struct wl_resource* resource) { + wl_resource_destroy(resource); +} + +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; + + 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); +} + +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}; + +static void sl_destroy_host_subsurface(struct wl_resource* resource) { + struct sl_host_subsurface* host = wl_resource_get_user_data(resource); + + wl_subsurface_destroy(host->proxy); + wl_resource_set_user_data(resource, NULL); + free(host); +} + +static void sl_subcompositor_destroy(struct wl_client* client, + struct wl_resource* resource) { + wl_resource_destroy(resource); +} + +static void sl_subcompositor_get_subsurface( + struct wl_client* client, + struct wl_resource* resource, + 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_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); + + host_subsurface->ctx = host->ctx; + host_subsurface->resource = + wl_resource_create(client, &wl_subsurface_interface, 1, id); + wl_resource_set_implementation(host_subsurface->resource, + &sl_subsurface_implementation, host_subsurface, + sl_destroy_host_subsurface); + host_subsurface->proxy = wl_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; +} + +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); + + wl_subcompositor_destroy(host->proxy); + wl_resource_set_user_data(resource, NULL); + free(host); +} + +static void sl_bind_host_subcompositor(struct wl_client* client, + void* data, + 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); + 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 = + wl_registry_bind(wl_display_get_registry(ctx->display), + 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 new file mode 100644 index 0000000..24b334e --- /dev/null +++ b/sommelier/sommelier-text-input.c @@ -0,0 +1,332 @@ +// 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-viewporter.c b/sommelier/sommelier-viewporter.c new file mode 100644 index 0000000..c12a306 --- /dev/null +++ b/sommelier/sommelier-viewporter.c @@ -0,0 +1,128 @@ +// 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 "viewporter-client-protocol.h" +#include "viewporter-server-protocol.h" + +struct sl_host_viewporter { + struct sl_viewporter* viewporter; + struct wl_resource* resource; + struct wp_viewporter* proxy; +}; + +struct sl_host_viewport { + struct wl_resource* resource; + struct sl_viewport viewport; +}; + +static void sl_viewport_destroy(struct wl_client* client, + struct wl_resource* resource) { + wl_resource_destroy(resource); +} + +static void sl_viewport_set_source(struct wl_client* client, + struct wl_resource* resource, + wl_fixed_t x, + wl_fixed_t y, + wl_fixed_t width, + wl_fixed_t height) { + struct sl_host_viewport* host = wl_resource_get_user_data(resource); + + host->viewport.src_x = x; + host->viewport.src_y = y; + host->viewport.src_width = width; + host->viewport.src_height = height; +} + +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); + + host->viewport.dst_width = width; + host->viewport.dst_height = height; +} + +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); + + wl_resource_set_user_data(resource, NULL); + wl_list_remove(&host->viewport.link); + free(host); +} + +static void sl_viewporter_destroy(struct wl_client* client, + struct wl_resource* resource) { + wl_resource_destroy(resource); +} + +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); + + host_viewport->viewport.src_x = -1; + host_viewport->viewport.src_y = -1; + host_viewport->viewport.src_width = -1; + host_viewport->viewport.src_height = -1; + host_viewport->viewport.dst_width = -1; + host_viewport->viewport.dst_height = -1; + wl_list_insert(&host_surface->contents_viewport, + &host_viewport->viewport.link); + host_viewport->resource = + wl_resource_create(client, &wp_viewport_interface, 1, id); + wl_resource_set_implementation(host_viewport->resource, + &sl_viewport_implementation, host_viewport, + sl_destroy_host_viewport); +} + +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); + + wp_viewporter_destroy(host->proxy); + wl_resource_set_user_data(resource, NULL); + free(host); +} + +static void sl_bind_host_viewporter(struct wl_client* client, + void* data, + 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); + 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 = + wl_registry_bind(wl_display_get_registry(ctx->display), + 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-xdg-shell.c b/sommelier/sommelier-xdg-shell.c new file mode 100644 index 0000000..a5551b2 --- /dev/null +++ b/sommelier/sommelier-xdg-shell.c @@ -0,0 +1,563 @@ +// 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(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); +} \ No newline at end of file diff --git a/sommelier/sommelier.c b/sommelier/sommelier.c new file mode 100644 index 0000000..f7a00d8 --- /dev/null +++ b/sommelier/sommelier.c @@ -0,0 +1,4146 @@ +// Copyright 2017 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 +#include +#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 "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" + +// Check that required macro definitions exist. +#ifndef XWAYLAND_PATH +#error XWAYLAND_PATH must be defined +#endif +#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 +#ifndef DARK_FRAME_COLOR +#error DARK_FRAME_COLOR must be defined +#endif + +struct sl_data_source { + struct sl_context* ctx; + 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_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 +#define MAX_SCALE 10.0 + +#define MIN_DPI 72 +#define MAX_DPI 9600 + +#define XCURSOR_SIZE_BASE 24 + +#ifndef UNIX_PATH_MAX +#define UNIX_PATH_MAX 108 +#endif + +#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 + +// 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; +} + +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); + } +} + +struct sl_sync_point* sl_sync_point_create(int fd) { + struct sl_sync_point* sync_point; + + sync_point = malloc(sizeof(*sync_point)); + sync_point->fd = fd; + sync_point->sync = NULL; + + return sync_point; +} + +void sl_sync_point_destroy(struct sl_sync_point* sync_point) +{ + close(sync_point->fd); + free(sync_point); +} + +static void sl_internal_xdg_shell_ping(void* data, + struct zxdg_shell_v6* xdg_shell, + uint32_t serial) { + zxdg_shell_v6_pong(xdg_shell, serial); +} + +static const struct zxdg_shell_v6_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) { + struct sl_context* ctx = window->ctx; + + // Clamp size to screen. + window->width = MIN(window->width, ctx->screen->width_in_pixels); + window->height = MIN(window->height, ctx->screen->height_in_pixels); +} + +static void sl_adjust_window_position_for_screen_size( + struct sl_window* window) { + struct sl_context* ctx = window->ctx; + + // Center horizontally/vertically. + window->x = ctx->screen->width_in_pixels / 2 - window->width / 2; + 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, + }, + }; + + if (!window->managed) + return; + + xcb_send_event(ctx->connection, 0, window->id, + XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, (char*)&event); + + xcb_set_input_focus(ctx->connection, XCB_INPUT_FOCUS_NONE, window->id, + XCB_CURRENT_TIME); + } else { + xcb_set_input_focus(ctx->connection, XCB_INPUT_FOCUS_NONE, XCB_NONE, + XCB_CURRENT_TIME); + } +} + +void sl_restack_windows(struct sl_context* ctx, uint32_t focus_resource_id) { + struct sl_window* sibling; + uint32_t values[1]; + + wl_list_for_each(sibling, &ctx->windows, link) { + if (!sibling->managed) + continue; + + // Move focus window to the top and all other windows to the bottom. + values[0] = sibling->host_surface_id == focus_resource_id + ? XCB_STACK_MODE_ABOVE + : XCB_STACK_MODE_BELOW; + xcb_configure_window(ctx->connection, sibling->frame_id, + XCB_CONFIG_WINDOW_STACK_MODE, values); + } +} + +void sl_roundtrip(struct sl_context* ctx) { + 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) { + struct sl_context* ctx = window->ctx; + uint32_t values[2]; + + values[0] = state; + values[1] = XCB_WINDOW_NONE; + + xcb_change_property(ctx->connection, XCB_PROP_MODE_REPLACE, window->id, + ctx->atoms[ATOM_WM_STATE].value, + 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); + } + } 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) { + wl_resource_destroy(resource); +} + +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); + + 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); + + if (host->proxy) + wl_buffer_destroy(host->proxy); + if (host->shm_mmap) { + host->shm_mmap->buffer_resource = NULL; + sl_mmap_unref(host->shm_mmap); + } + if (host->sync_point) { + sl_sync_point_destroy(host->sync_point); + } + wl_resource_set_user_data(resource, NULL); + free(host); +} + +struct sl_host_buffer* sl_create_host_buffer(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); + + host_buffer->width = width; + host_buffer->height = height; + host_buffer->resource = + wl_resource_create(client, &wl_buffer_interface, 1, id); + wl_resource_set_implementation(host_buffer->resource, + &sl_buffer_implementation, host_buffer, + sl_destroy_host_buffer); + host_buffer->shm_mmap = NULL; + 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; + + return host_buffer; +} + +static void sl_internal_data_offer_destroy(struct sl_data_offer* host) { + wl_data_offer_destroy(host->internal); + wl_array_release(&host->atoms); + wl_array_release(&host->cookies); + free(host); +} + +static void sl_set_selection(struct sl_context* ctx, + struct sl_data_offer* data_offer) { + if (ctx->selection_data_offer) { + sl_internal_data_offer_destroy(ctx->selection_data_offer); + ctx->selection_data_offer = NULL; + } + + if (ctx->clipboard_manager) { + if (!data_offer) { + if (ctx->selection_owner == ctx->selection_window) + xcb_set_selection_owner(ctx->connection, XCB_ATOM_NONE, + ctx->atoms[ATOM_CLIPBOARD].value, + ctx->selection_timestamp); + return; + } + + 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; + for (int i = 0; i < atoms; i++) { + xcb_intern_atom_cookie_t cookie = + ((xcb_intern_atom_cookie_t*)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; + free(reply); + } + } + + xcb_set_selection_owner(ctx->connection, ctx->selection_window, + ctx->atoms[ATOM_CLIPBOARD].value, XCB_CURRENT_TIME); + } + + ctx->selection_data_offer = data_offer; +} + +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)); + *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) {} + +static void sl_internal_data_offer_action(void* data, + struct wl_data_offer* data_offer, + uint32_t dnd_action) {} + +static const struct wl_data_offer_listener sl_internal_data_offer_listener = { + sl_internal_data_offer_offer, sl_internal_data_offer_source_actions, + sl_internal_data_offer_action}; + +static void sl_internal_data_device_data_offer( + void* data, + 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); + + host_data_offer->ctx = ctx; + host_data_offer->internal = data_offer; + wl_array_init(&host_data_offer->atoms); + wl_array_init(&host_data_offer->cookies); + + wl_data_offer_add_listener(host_data_offer->internal, + &sl_internal_data_offer_listener, host_data_offer); +} + +static void sl_internal_data_device_enter(void* data, + struct wl_data_device* data_device, + uint32_t serial, + struct wl_surface* surface, + wl_fixed_t x, + wl_fixed_t y, + struct wl_data_offer* data_offer) {} + +static void sl_internal_data_device_leave(void* data, + struct wl_data_device* data_device) {} + +static void sl_internal_data_device_motion(void* data, + struct wl_data_device* data_device, + uint32_t time, + wl_fixed_t x, + wl_fixed_t y) {} + +static void sl_internal_data_device_drop(void* data, + struct wl_data_device* data_device) {} + +static void sl_internal_data_device_selection( + void* data, + 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 = + data_offer ? wl_data_offer_get_user_data(data_offer) : NULL; + + sl_set_selection(ctx, host_data_offer); +} + +static const struct wl_data_device_listener sl_internal_data_device_listener = { + sl_internal_data_device_data_offer, sl_internal_data_device_enter, + sl_internal_data_device_leave, sl_internal_data_device_motion, + sl_internal_data_device_drop, sl_internal_data_device_selection}; + +void sl_host_seat_added(struct sl_host_seat* host) { + struct sl_context* ctx = host->seat->ctx; + + if (ctx->default_seat) + return; + + ctx->default_seat = host; + + // Get data device for selections. + if (ctx->data_device_manager && ctx->data_device_manager->internal) { + ctx->selection_data_device = wl_data_device_manager_get_data_device( + ctx->data_device_manager->internal, host->proxy); + wl_data_device_add_listener(ctx->selection_data_device, + &sl_internal_data_device_listener, ctx); + } +} + +void sl_host_seat_removed(struct sl_host_seat* host) { + 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); + } + + return global; +} + +static void sl_global_destroy(struct sl_global* global) { + struct sl_host_registry* registry; + + wl_list_for_each(registry, &global->ctx->registries, link) + 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) { + struct sl_context* ctx = (struct sl_context*)data; + + 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); + } else if (strcmp(interface, "wl_subcompositor") == 0) { + struct sl_subcompositor* subcompositor = + malloc(sizeof(struct sl_subcompositor)); + assert(subcompositor); + subcompositor->ctx = ctx; + subcompositor->id = id; + assert(!ctx->subcompositor); + 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)); + assert(shm); + shm->ctx = ctx; + shm->id = id; + shm->internal = 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)); + assert(shell); + shell->ctx = ctx; + shell->id = id; + assert(!ctx->shell); + 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)); + 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); + } else if (strcmp(interface, "wl_seat") == 0) { + struct sl_seat* seat = malloc(sizeof(struct sl_seat)); + assert(seat); + seat->ctx = ctx; + seat->id = id; + seat->version = MIN(5, version); + seat->last_serial = 0; + seat->host_global = sl_seat_global_create(seat); + 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)); + 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, "wl_data_device_manager") == 0) { + struct sl_data_device_manager* data_device_manager = + malloc(sizeof(struct sl_data_device_manager)); + assert(data_device_manager); + data_device_manager->ctx = ctx; + data_device_manager->id = id; + data_device_manager->version = MIN(3, version); + data_device_manager->internal = NULL; + data_device_manager->host_global = NULL; + assert(!ctx->data_device_manager); + ctx->data_device_manager = data_device_manager; + if (ctx->xwayland) { + data_device_manager->internal = + wl_registry_bind(registry, id, &wl_data_device_manager_interface, + 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)); + assert(xdg_shell); + xdg_shell->ctx = ctx; + xdg_shell->id = id; + xdg_shell->internal = NULL; + xdg_shell->host_global = NULL; + 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); + } 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)); + assert(aura_shell); + aura_shell->ctx = ctx; + aura_shell->id = id; + aura_shell->version = MIN(MIN_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); + 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)); + assert(viewporter); + viewporter->ctx = ctx; + viewporter->id = id; + viewporter->host_viewporter_global = NULL; + viewporter->internal = + wl_registry_bind(registry, id, &wp_viewporter_interface, 1); + assert(!ctx->viewporter); + ctx->viewporter = viewporter; + viewporter->host_viewporter_global = sl_viewporter_global_create(ctx); + // Allow non-integer scale. + 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)); + 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); + assert(!ctx->linux_dmabuf); + ctx->linux_dmabuf = linux_dmabuf; + linux_dmabuf->host_drm_global = sl_drm_global_create(ctx); + } else if (strcmp(interface, "zcr_keyboard_extension_v1") == 0) { + struct sl_keyboard_extension* keyboard_extension = + 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); + 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)); + 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); + assert(!ctx->text_input_manager); + ctx->text_input_manager = text_input_manager; + } +} + +static void sl_registry_remover(void* data, + struct wl_registry* registry, + uint32_t id) { + struct sl_context* ctx = (struct sl_context*)data; + struct sl_output* output; + struct sl_seat* seat; + + if (ctx->compositor && ctx->compositor->id == id) { + sl_global_destroy(ctx->compositor->host_global); + wl_compositor_destroy(ctx->compositor->internal); + free(ctx->compositor); + ctx->compositor = NULL; + return; + } + if (ctx->subcompositor && ctx->subcompositor->id == id) { + sl_global_destroy(ctx->subcompositor->host_global); + wl_shm_destroy(ctx->shm->internal); + free(ctx->subcompositor); + ctx->subcompositor = NULL; + return; + } + if (ctx->shm && ctx->shm->id == id) { + sl_global_destroy(ctx->shm->host_global); + free(ctx->shm); + ctx->shm = NULL; + return; + } + if (ctx->shell && ctx->shell->id == id) { + sl_global_destroy(ctx->shell->host_global); + free(ctx->shell); + ctx->shell = NULL; + return; + } + if (ctx->data_device_manager && ctx->data_device_manager->id == id) { + if (ctx->data_device_manager->host_global) + sl_global_destroy(ctx->data_device_manager->host_global); + if (ctx->data_device_manager->internal) + wl_data_device_manager_destroy(ctx->data_device_manager->internal); + free(ctx->data_device_manager); + ctx->data_device_manager = NULL; + return; + } + if (ctx->xdg_shell && ctx->xdg_shell->id == id) { + 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); + free(ctx->xdg_shell); + ctx->xdg_shell = NULL; + return; + } + if (ctx->aura_shell && ctx->aura_shell->id == id) { + if (ctx->aura_shell->host_gtk_shell_global) + sl_global_destroy(ctx->aura_shell->host_gtk_shell_global); + zaura_shell_destroy(ctx->aura_shell->internal); + free(ctx->aura_shell); + ctx->aura_shell = NULL; + return; + } + if (ctx->viewporter && ctx->viewporter->id == id) { + if (ctx->viewporter->host_viewporter_global) + sl_global_destroy(ctx->viewporter->host_viewporter_global); + wp_viewporter_destroy(ctx->viewporter->internal); + free(ctx->viewporter); + ctx->viewporter = NULL; + return; + } + if (ctx->linux_dmabuf && ctx->linux_dmabuf->id == id) { + if (ctx->linux_dmabuf->host_drm_global) + sl_global_destroy(ctx->linux_dmabuf->host_drm_global); + zwp_linux_dmabuf_v1_destroy(ctx->linux_dmabuf->internal); + free(ctx->linux_dmabuf); + ctx->linux_dmabuf = NULL; + return; + } + if (ctx->keyboard_extension && ctx->keyboard_extension->id == id) { + zcr_keyboard_extension_v1_destroy(ctx->keyboard_extension->internal); + free(ctx->keyboard_extension); + ctx->keyboard_extension = NULL; + return; + } + if (ctx->text_input_manager && ctx->text_input_manager->id == id) { + sl_global_destroy(ctx->text_input_manager->host_global); + free(ctx->text_input_manager); + ctx->text_input_manager = NULL; + return; + } + if (ctx->relative_pointer_manager && + ctx->relative_pointer_manager->id == id) { + sl_global_destroy(ctx->relative_pointer_manager->host_global); + free(ctx->relative_pointer_manager); + ctx->relative_pointer_manager = NULL; + return; + } + wl_list_for_each(output, &ctx->outputs, link) { + if (output->id == id) { + sl_global_destroy(output->host_global); + wl_list_remove(&output->link); + free(output); + return; + } + } + wl_list_for_each(seat, &ctx->seats, link) { + if (seat->id == id) { + sl_global_destroy(seat->host_global); + wl_list_remove(&seat->link); + free(seat); + return; + } + } + + // Not reached. + assert(0); +} + +static 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) { + struct sl_context* ctx = (struct sl_context*)data; + int count = 0; + + if ((mask & WL_EVENT_HANGUP) || (mask & WL_EVENT_ERROR)) { + wl_client_flush(ctx->client); + exit(EXIT_SUCCESS); + } + + 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; +} + +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)); + 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->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); +} + +static void sl_destroy_window(struct sl_window* 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); + if (window->xdg_toplevel) + zxdg_toplevel_v6_destroy(window->xdg_toplevel); + if (window->xdg_surface) + zxdg_surface_v6_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); +} + +static int sl_is_window(struct sl_window* window, xcb_window_t id) { + if (window->id == id) + return 1; + + if (window->frame_id != XCB_WINDOW_NONE) { + if (window->frame_id == id) + return 1; + } + + return 0; +} + +static 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) { + if (sl_is_window(window, id)) + return window; + } + wl_list_for_each(window, &ctx->unpaired_windows, link) { + if (sl_is_window(window, id)) + return window; + } + return NULL; +} + +static 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; +} + +static void sl_handle_create_notify(struct sl_context* ctx, + xcb_create_notify_event_t* event) { + if (sl_is_our_window(ctx, event->window)) + return; + + sl_create_window(ctx, event->window, event->x, event->y, event->width, + event->height, event->border_width); +} + +static 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)) + return; + + window = sl_lookup_window(ctx, event->window); + if (!window) + return; + + sl_destroy_window(window); +} + +static 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) { + int width = 1; + int height = 1; + int border_width = 0; + + // Return early if window is already tracked. This happens when we + // reparent an unampped window back to the root window. + window = sl_lookup_window(ctx, event->window); + if (window) + return; + + xcb_get_geometry_reply_t* geometry_reply = xcb_get_geometry_reply( + ctx->connection, xcb_get_geometry(ctx->connection, event->window), + NULL); + + if (geometry_reply) { + width = geometry_reply->width; + height = geometry_reply->height; + border_width = geometry_reply->border_width; + free(geometry_reply); + } + sl_create_window(ctx, event->window, event->x, event->y, width, height, + border_width); + return; + } + + if (sl_is_our_window(ctx, event->parent)) + return; + + window = sl_lookup_window(ctx, event->window); + if (!window) + return; + + sl_destroy_window(window); +} + +static void sl_handle_map_request(struct sl_context* ctx, + xcb_map_request_event_t* event) { + 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_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_MOTIF_WM_HINTS, ctx->atoms[ATOM_MOTIF_WM_HINTS].value}, + {PROPERTY_NET_STARTUP_ID, ctx->atoms[ATOM_NET_STARTUP_ID].value}, + {PROPERTY_GTK_THEME_VARIANT, ctx->atoms[ATOM_GTK_THEME_VARIANT].value}, + }; + 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}; + uint32_t values[5]; + int i; + + if (!window) + return; + + assert(!sl_is_our_window(ctx, event->window)); + + 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) { + property_cookies[i] = + xcb_get_property(ctx->connection, 0, window->id, properties[i].atom, + XCB_ATOM_ANY, 0, 2048); + } + + if (window->frame_id == XCB_WINDOW_NONE) { + xcb_get_geometry_reply_t* geometry_reply = + xcb_get_geometry_reply(ctx->connection, geometry_cookie, NULL); + if (geometry_reply) { + window->x = geometry_reply->x; + window->y = geometry_reply->y; + window->width = geometry_reply->width; + window->height = geometry_reply->height; + window->depth = geometry_reply->depth; + free(geometry_reply); + } + } + + free(window->name); + window->name = NULL; + free(window->clazz); + window->clazz = NULL; + free(window->startup_id); + window->startup_id = NULL; + window->transient_for = XCB_WINDOW_NONE; + window->client_leader = XCB_WINDOW_NONE; + window->decorated = 1; + window->size_flags = 0; + window->dark_frame = 0; + + for (i = 0; i < ARRAY_SIZE(properties); ++i) { + xcb_get_property_reply_t* reply = + xcb_get_property_reply(ctx->connection, property_cookies[i], NULL); + + if (!reply) + continue; + + if (reply->type == XCB_ATOM_NONE) { + free(reply); + continue; + } + + 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); + } + } break; + case PROPERTY_WM_TRANSIENT_FOR: + if (xcb_get_property_value_length(reply) >= 4) + window->transient_for = *((uint32_t *)xcb_get_property_value(reply)); + break; + case PROPERTY_WM_NORMAL_HINTS: + if (xcb_get_property_value_length(reply) >= 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)); + break; + case PROPERTY_MOTIF_WM_HINTS: + if (xcb_get_property_value_length(reply) >= 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)); + break; + case PROPERTY_GTK_THEME_VARIANT: + if (xcb_get_property_value_length(reply) >= 4) + window->dark_frame = !strcmp(xcb_get_property_value(reply), "dark"); + break; + default: + break; + } + free(reply); + } + + if (mwm_hints.flags & MWM_HINTS_DECORATIONS) { + if (mwm_hints.decorations & MWM_DECOR_ALL) + window->decorated = ~mwm_hints.decorations & MWM_DECOR_TITLE; + else + window->decorated = mwm_hints.decorations & MWM_DECOR_TITLE; + } + + // Allow user/program controlled position for transients. + if (window->transient_for) + window->size_flags |= size_hints.flags & (US_POSITION | P_POSITION); + + // If startup ID is not set, then try the client leader window. + if (!window->startup_id && window->client_leader) { + xcb_get_property_reply_t* reply = xcb_get_property_reply( + ctx->connection, + xcb_get_property(ctx->connection, 0, window->client_leader, + ctx->atoms[ATOM_NET_STARTUP_ID].value, XCB_ATOM_ANY, 0, + 2048), + NULL); + if (reply) { + if (reply->type != XCB_ATOM_NONE) { + window->startup_id = strndup(xcb_get_property_value(reply), + xcb_get_property_value_length(reply)); + } + free(reply); + } + } + + window->size_flags |= size_hints.flags & (P_MIN_SIZE | P_MAX_SIZE); + if (window->size_flags & P_MIN_SIZE) { + window->min_width = size_hints.min_width; + window->min_height = size_hints.min_height; + } + if (window->size_flags & P_MAX_SIZE) { + window->max_width = size_hints.max_width; + window->max_height = size_hints.max_height; + } + + window->border_width = 0; + sl_adjust_window_size_for_screen_size(window); + if (!(window->size_flags & (US_POSITION | P_POSITION))) + sl_adjust_window_position_for_screen_size(window); + + values[0] = window->width; + values[1] = window->height; + values[2] = 0; + xcb_configure_window(ctx->connection, window->id, + XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT | + XCB_CONFIG_WINDOW_BORDER_WIDTH, + values); + // This needs to match the frame extents of the X11 frame window used + // for reparenting or applications tend to be confused. The actual window + // frame size used by the host compositor can be different. + values[0] = 0; + values[1] = 0; + values[2] = 0; + values[3] = 0; + xcb_change_property(ctx->connection, XCB_PROP_MODE_REPLACE, window->id, + ctx->atoms[ATOM_NET_FRAME_EXTENTS].value, + XCB_ATOM_CARDINAL, 32, 4, values); + + // Remove weird gravities. + values[0] = XCB_GRAVITY_NORTH_WEST; + xcb_change_window_attributes(ctx->connection, window->id, XCB_CW_WIN_GRAVITY, + values); + + if (window->frame_id == XCB_WINDOW_NONE) { + int depth = window->depth ? window->depth : ctx->screen->root_depth; + + values[0] = ctx->screen->black_pixel; + values[1] = XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | + XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT; + values[2] = ctx->colormaps[depth]; + + window->frame_id = xcb_generate_id(ctx->connection); + xcb_create_window( + ctx->connection, depth, window->frame_id, ctx->screen->root, window->x, + window->y, window->width, window->height, 0, + XCB_WINDOW_CLASS_INPUT_OUTPUT, ctx->visual_ids[depth], + XCB_CW_BORDER_PIXEL | XCB_CW_EVENT_MASK | XCB_CW_COLORMAP, values); + values[0] = XCB_STACK_MODE_BELOW; + xcb_configure_window(ctx->connection, window->frame_id, + XCB_CONFIG_WINDOW_STACK_MODE, values); + xcb_reparent_window(ctx->connection, window->id, window->frame_id, 0, 0); + } else { + values[0] = window->x; + values[1] = window->y; + values[2] = window->width; + values[3] = window->height; + values[4] = XCB_STACK_MODE_BELOW; + xcb_configure_window( + ctx->connection, window->frame_id, + XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_WIDTH | + XCB_CONFIG_WINDOW_HEIGHT | XCB_CONFIG_WINDOW_STACK_MODE, + values); + } + + sl_window_set_wm_state(window, WM_STATE_NORMAL); + sl_send_configure_notify(window); + + xcb_map_window(ctx->connection, window->id); + xcb_map_window(ctx->connection, window->frame_id); +} + +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) { + struct sl_window* window; + + if (sl_is_our_window(ctx, event->window)) + return; + + if (event->response_type & SEND_EVENT_MASK) + return; + + window = sl_lookup_window(ctx, event->window); + if (!window) + return; + + if (ctx->host_focus_window == window) { + ctx->host_focus_window = NULL; + ctx->needs_set_input_focus = 1; + } + + if (window->host_surface_id) { + window->host_surface_id = 0; + sl_window_update(window); + } + + sl_window_set_wm_state(window, WM_STATE_WITHDRAWN); + + // Reparent window and destroy frame if it exists. + if (window->frame_id != XCB_WINDOW_NONE) { + xcb_reparent_window(ctx->connection, window->id, ctx->screen->root, + window->x, window->y); + xcb_destroy_window(ctx->connection, window->frame_id); + window->frame_id = XCB_WINDOW_NONE; + } + + // Reset properties to unmanaged state in case the window transitions to + // an override-redirect window. + window->managed = 0; + window->decorated = 0; + window->size_flags = P_POSITION; +} + +static 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); + int width = window->width; + int height = window->height; + uint32_t values[7]; + + assert(!sl_is_our_window(ctx, event->window)); + + if (!window->managed) { + int i = 0; + + if (event->value_mask & XCB_CONFIG_WINDOW_X) + values[i++] = event->x; + if (event->value_mask & XCB_CONFIG_WINDOW_Y) + values[i++] = event->y; + if (event->value_mask & XCB_CONFIG_WINDOW_WIDTH) + values[i++] = event->width; + if (event->value_mask & XCB_CONFIG_WINDOW_HEIGHT) + values[i++] = event->height; + if (event->value_mask & XCB_CONFIG_WINDOW_BORDER_WIDTH) + values[i++] = event->border_width; + if (event->value_mask & XCB_CONFIG_WINDOW_SIBLING) + values[i++] = event->sibling; + if (event->value_mask & XCB_CONFIG_WINDOW_STACK_MODE) + values[i++] = event->stack_mode; + + xcb_configure_window(ctx->connection, window->id, event->value_mask, + values); + return; + } + + // Ack configure events as satisfying request removes the guarantee + // 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); + 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); + window->next_config.serial = 0; + window->next_config.mask = 0; + window->next_config.states_length = 0; + } + } + + if (event->value_mask & XCB_CONFIG_WINDOW_X) + window->x = event->x; + if (event->value_mask & XCB_CONFIG_WINDOW_Y) + window->y = event->y; + + if (window->allow_resize) { + if (event->value_mask & XCB_CONFIG_WINDOW_WIDTH) + window->width = event->width; + if (event->value_mask & XCB_CONFIG_WINDOW_HEIGHT) + window->height = event->height; + } + + sl_adjust_window_size_for_screen_size(window); + if (window->size_flags & (US_POSITION | P_POSITION)) + sl_window_update(window); + else + sl_adjust_window_position_for_screen_size(window); + + values[0] = window->x; + values[1] = window->y; + values[2] = window->width; + values[3] = window->height; + values[4] = 0; + xcb_configure_window(ctx->connection, window->frame_id, + XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | + XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, + values); + + // We need to send a synthetic configure notify if: + // - Not changing the size, location, border width. + // - Moving the window without resizing it or changing its border width. + if (width != window->width || height != window->height || + window->border_width) { + xcb_configure_window(ctx->connection, window->id, + XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT | + XCB_CONFIG_WINDOW_BORDER_WIDTH, + &values[2]); + window->border_width = 0; + } else { + sl_send_configure_notify(window); + } +} + +static void sl_handle_configure_notify(struct sl_context* ctx, + xcb_configure_notify_event_t* event) { + struct sl_window* window; + + if (sl_is_our_window(ctx, event->window)) + return; + + if (event->window == ctx->screen->root) { + xcb_get_geometry_reply_t* geometry_reply = xcb_get_geometry_reply( + ctx->connection, xcb_get_geometry(ctx->connection, event->window), + NULL); + int width = ctx->screen->width_in_pixels; + int height = ctx->screen->height_in_pixels; + + if (geometry_reply) { + width = geometry_reply->width; + height = geometry_reply->height; + free(geometry_reply); + } + + if (width == ctx->screen->width_in_pixels || + height == ctx->screen->height_in_pixels) { + return; + } + + ctx->screen->width_in_pixels = width; + ctx->screen->height_in_pixels = height; + + // Re-center managed windows. + wl_list_for_each(window, &ctx->windows, link) { + int x, y; + + if (window->size_flags & (US_POSITION | P_POSITION)) + continue; + + x = window->x; + y = window->y; + sl_adjust_window_position_for_screen_size(window); + if (window->x != x || window->y != y) { + uint32_t values[2]; + + values[0] = window->x; + values[1] = window->y; + xcb_configure_window(ctx->connection, window->frame_id, + XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, values); + sl_send_configure_notify(window); + } + } + return; + } + + window = sl_lookup_window(ctx, event->window); + if (!window) + return; + + if (window->managed) + return; + + window->width = event->width; + window->height = event->height; + window->border_width = event->border_width; + if (event->x != window->x || event->y != window->y) { + window->x = event->x; + window->y = event->y; + sl_window_update(window); + } +} + +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; + case NET_WM_MOVERESIZE_SIZE_TOP: + return ZXDG_TOPLEVEL_V6_RESIZE_EDGE_TOP; + case NET_WM_MOVERESIZE_SIZE_TOPRIGHT: + return ZXDG_TOPLEVEL_V6_RESIZE_EDGE_TOP_RIGHT; + case NET_WM_MOVERESIZE_SIZE_RIGHT: + return ZXDG_TOPLEVEL_V6_RESIZE_EDGE_RIGHT; + case NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT: + return ZXDG_TOPLEVEL_V6_RESIZE_EDGE_BOTTOM_RIGHT; + case NET_WM_MOVERESIZE_SIZE_BOTTOM: + return ZXDG_TOPLEVEL_V6_RESIZE_EDGE_BOTTOM; + case NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT: + return ZXDG_TOPLEVEL_V6_RESIZE_EDGE_BOTTOM_LEFT; + case NET_WM_MOVERESIZE_SIZE_LEFT: + return ZXDG_TOPLEVEL_V6_RESIZE_EDGE_LEFT; + default: + return ZXDG_TOPLEVEL_V6_RESIZE_EDGE_NONE; + } +} + +static void sl_handle_client_message(struct sl_context* ctx, + xcb_client_message_event_t* event) { + if (event->type == ctx->atoms[ATOM_WL_SURFACE_ID].value) { + struct sl_window *window, *unpaired_window = NULL; + + wl_list_for_each(window, &ctx->unpaired_windows, link) { + if (sl_is_window(window, event->window)) { + unpaired_window = window; + break; + } + } + + if (unpaired_window) { + unpaired_window->host_surface_id = event->data.data32[0]; + sl_window_update(unpaired_window); + } + } else if (event->type == ctx->atoms[ATOM_NET_WM_MOVERESIZE].value) { + struct sl_window* window = sl_lookup_window(ctx, event->window); + + if (window && window->xdg_toplevel) { + struct sl_host_seat* seat = window->ctx->default_seat; + + if (!seat) + return; + + if (event->data.data32[2] == NET_WM_MOVERESIZE_MOVE) { + zxdg_toplevel_v6_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) + return; + + zxdg_toplevel_v6_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) { + int changed[ATOM_LAST + 1]; + uint32_t action = event->data.data32[0]; + int i; + + for (i = 0; i < ARRAY_SIZE(ctx->atoms); ++i) { + changed[i] = event->data.data32[1] == ctx->atoms[i].value || + event->data.data32[2] == ctx->atoms[i].value; + } + + 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); + } + + 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); + } + } + } 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); + if (window && window->xdg_toplevel) { + zxdg_toplevel_v6_set_minimized(window->xdg_toplevel); + } + } +} + +static 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); + } +} + +static void sl_handle_focus_out(struct sl_context* ctx, + xcb_focus_out_event_t* event) {} + +int sl_begin_data_source_send(struct sl_context* ctx, + int fd, + xcb_intern_atom_cookie_t cookie, + struct sl_data_source* data_source) { + xcb_intern_atom_reply_t* reply = + xcb_intern_atom_reply(ctx->connection, cookie, NULL); + + if (!reply) { + close(fd); + return 0; + } + + int flags, rv; + + xcb_convert_selection(ctx->connection, ctx->selection_window, + ctx->atoms[ATOM_CLIPBOARD].value, reply->atom, + ctx->atoms[ATOM_WL_SELECTION].value, XCB_CURRENT_TIME); + + flags = fcntl(fd, F_GETFL, 0); + rv = fcntl(fd, F_SETFL, flags | O_NONBLOCK); + assert(!rv); + UNUSED(rv); + + ctx->selection_data_source_send_fd = fd; + free(reply); + return 1; +} + +void sl_process_data_source_send_pending_list(struct sl_context* ctx) { + while (!wl_list_empty(&ctx->selection_data_source_send_pending)) { + struct wl_list* next = ctx->selection_data_source_send_pending.next; + struct sl_data_source_send_request* request; + request = wl_container_of(next, request, link); + wl_list_remove(next); + + int rv = sl_begin_data_source_send(ctx, request->fd, request->cookie, + request->data_source); + free(request); + if (rv) + break; + } +} + +static int sl_handle_selection_fd_writable(int fd, uint32_t mask, void* data) { + struct sl_context* ctx = data; + uint8_t *value; + int bytes, bytes_left; + + value = xcb_get_property_value(ctx->selection_property_reply); + bytes_left = xcb_get_property_value_length(ctx->selection_property_reply) - + ctx->selection_property_offset; + + bytes = write(fd, value + ctx->selection_property_offset, bytes_left); + if (bytes == -1) { + fprintf(stderr, "write error to target fd: %m\n"); + close(fd); + fd = -1; + } else if (bytes == bytes_left) { + if (ctx->selection_incremental_transfer) { + xcb_delete_property(ctx->connection, ctx->selection_window, + ctx->atoms[ATOM_WL_SELECTION].value); + } else { + close(fd); + fd = -1; + } + } else { + ctx->selection_property_offset += bytes; + return 1; + } + + 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; + } + if (fd < 0) { + ctx->selection_data_source_send_fd = -1; + sl_process_data_source_send_pending_list(ctx); + } + return 1; +} + +static void sl_write_selection_property(struct sl_context* ctx, + xcb_get_property_reply_t* reply) { + ctx->selection_property_offset = 0; + ctx->selection_property_reply = reply; + sl_handle_selection_fd_writable(ctx->selection_data_source_send_fd, + WL_EVENT_WRITABLE, ctx); + + if (!ctx->selection_property_reply) + return; + + assert(!ctx->selection_send_event_source); + ctx->selection_send_event_source = 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); +} + +static void sl_send_selection_notify(struct sl_context* ctx, + xcb_atom_t property) { + xcb_selection_notify_event_t event = { + .response_type = XCB_SELECTION_NOTIFY, + .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}; + + xcb_send_event(ctx->connection, 0, ctx->selection_request.requestor, + XCB_EVENT_MASK_NO_EVENT, (char*)&event); +} + +static void sl_send_selection_data(struct sl_context* ctx) { + assert(!ctx->selection_data_ack_pending); + xcb_change_property( + ctx->connection, XCB_PROP_MODE_REPLACE, ctx->selection_request.requestor, + ctx->selection_request.property, ctx->selection_data_type, + /*format=*/8, ctx->selection_data.size, ctx->selection_data.data); + ctx->selection_data_ack_pending = 1; + ctx->selection_data.size = 0; +} + +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; + + 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; + + 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); + ctx->selection_data_offer_receive_fd = -1; + close(fd); + } else { + ctx->selection_data.size = offset + bytes; + if (ctx->selection_data.size >= sl_incr_chunk_size) { + if (!ctx->selection_incremental_transfer) { + ctx->selection_incremental_transfer = 1; + xcb_change_property( + ctx->connection, XCB_PROP_MODE_REPLACE, + ctx->selection_request.requestor, ctx->selection_request.property, + ctx->atoms[ATOM_INCR].value, 32, 1, &sl_incr_chunk_size); + ctx->selection_data_ack_pending = 1; + sl_send_selection_notify(ctx, ctx->selection_request.property); + } else if (!ctx->selection_data_ack_pending) { + sl_send_selection_data(ctx); + } + } else if (bytes == 0) { + if (!ctx->selection_data_ack_pending) + sl_send_selection_data(ctx); + if (!ctx->selection_incremental_transfer) { + sl_send_selection_notify(ctx, ctx->selection_request.property); + ctx->selection_request.requestor = XCB_NONE; + wl_array_release(&ctx->selection_data); + } + xcb_flush(ctx->connection); + ctx->selection_data_offer_receive_fd = -1; + close(fd); + } else { + ctx->selection_data.size = offset + bytes; + return 1; + } + } + + wl_event_source_remove(ctx->selection_event_source); + ctx->selection_event_source = NULL; + 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) { + struct sl_window* window = sl_lookup_window(ctx, event->window); + if (!window) + return; + + if (window->name) { + free(window->name); + window->name = NULL; + } + + if (event->state != XCB_PROPERTY_DELETE) { + 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), + NULL); + if (reply) { + window->name = strndup(xcb_get_property_value(reply), + xcb_get_property_value_length(reply)); + free(reply); + } + } + + if (!window->xdg_toplevel) + return; + + if (window->name) { + zxdg_toplevel_v6_set_title(window->xdg_toplevel, window->name); + } else { + zxdg_toplevel_v6_set_title(window->xdg_toplevel, ""); + } + } else if (event->atom == XCB_ATOM_WM_NORMAL_HINTS) { + struct sl_window* window = sl_lookup_window(ctx, event->window); + if (!window) + return; + + window->size_flags &= ~(P_MIN_SIZE | P_MAX_SIZE); + + if (event->state != XCB_PROPERTY_DELETE) { + struct sl_wm_size_hints size_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_NORMAL_HINTS, XCB_ATOM_ANY, 0, + sizeof(size_hints)), + NULL); + if (reply) { + memcpy(&size_hints, xcb_get_property_value(reply), sizeof(size_hints)); + free(reply); + } + + window->size_flags |= size_hints.flags & (P_MIN_SIZE | P_MAX_SIZE); + if (window->size_flags & P_MIN_SIZE) { + window->min_width = size_hints.min_width; + window->min_height = size_hints.min_height; + } + if (window->size_flags & P_MAX_SIZE) { + window->max_width = size_hints.max_width; + window->max_height = size_hints.max_height; + } + } + + if (!window->xdg_toplevel) + 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); + } else { + zxdg_toplevel_v6_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); + } else { + zxdg_toplevel_v6_set_max_size(window->xdg_toplevel, 0, 0); + } + } else if (event->atom == ctx->atoms[ATOM_MOTIF_WM_HINTS].value) { + struct sl_window* window = sl_lookup_window(ctx, event->window); + if (!window) + return; + + // Managed windows are decorated by default. + window->decorated = window->managed; + + if (event->state != XCB_PROPERTY_DELETE) { + struct sl_mwm_hints mwm_hints = {0}; + xcb_get_property_reply_t* reply = xcb_get_property_reply( + ctx->connection, + xcb_get_property(ctx->connection, 0, window->id, + ctx->atoms[ATOM_MOTIF_WM_HINTS].value, XCB_ATOM_ANY, + 0, sizeof(mwm_hints)), + NULL); + if (reply) { + if (mwm_hints.flags & MWM_HINTS_DECORATIONS) { + if (mwm_hints.decorations & MWM_DECOR_ALL) + window->decorated = ~mwm_hints.decorations & MWM_DECOR_TITLE; + else + window->decorated = mwm_hints.decorations & MWM_DECOR_TITLE; + } + } + } + + if (!window->aura_surface) + 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); + } else if (event->atom == ctx->atoms[ATOM_GTK_THEME_VARIANT].value) { + struct sl_window* window; + uint32_t frame_color; + + window = sl_lookup_window(ctx, event->window); + if (!window) + return; + + window->dark_frame = 0; + + if (event->state != XCB_PROPERTY_DELETE) { + xcb_get_property_reply_t* reply = xcb_get_property_reply( + ctx->connection, + xcb_get_property(ctx->connection, 0, window->id, + ctx->atoms[ATOM_GTK_THEME_VARIANT].value, + XCB_ATOM_ANY, 0, 2048), + NULL); + if (reply) { + if (xcb_get_property_value_length(reply) >= 4) + window->dark_frame = !strcmp(xcb_get_property_value(reply), "dark"); + free(reply); + } + } + + if (!window->aura_surface) + return; + + 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_WL_SELECTION].value) { + if (event->window == ctx->selection_window && + event->state == XCB_PROPERTY_NEW_VALUE && + ctx->selection_incremental_transfer) { + xcb_get_property_reply_t* reply = xcb_get_property_reply( + ctx->connection, + xcb_get_property(ctx->connection, 0, ctx->selection_window, + ctx->atoms[ATOM_WL_SELECTION].value, + XCB_GET_PROPERTY_TYPE_ANY, 0, 0x1fffffff), + NULL); + + if (!reply) + return; + + if (xcb_get_property_value_length(reply) > 0) { + sl_write_selection_property(ctx, reply); + } else { + assert(!ctx->selection_send_event_source); + close(ctx->selection_data_source_send_fd); + ctx->selection_data_source_send_fd = -1; + free(reply); + + sl_process_data_source_send_pending_list(ctx); + } + } + } else if (event->atom == ctx->selection_request.property) { + if (event->window == ctx->selection_request.requestor && + event->state == XCB_PROPERTY_DELETE && + ctx->selection_incremental_transfer) { + int data_size = ctx->selection_data.size; + + ctx->selection_data_ack_pending = 0; + + // Handle the case when there's more data to be received. + if (ctx->selection_data_offer_receive_fd >= 0) { + // Avoid sending empty data until transfer is complete. + if (data_size) + sl_send_selection_data(ctx); + + if (!ctx->selection_event_source) { + ctx->selection_event_source = 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); + } + return; + } + + sl_send_selection_data(ctx); + + // Release data if transfer is complete. + if (!data_size) { + ctx->selection_request.requestor = XCB_NONE; + wl_array_release(&ctx->selection_data); + } + } + } +} + +static void sl_internal_data_source_target(void* data, + struct wl_data_source* data_source, + const char* mime_type) {} + +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; + struct sl_context* ctx = host->ctx; + + xcb_intern_atom_cookie_t cookie = + xcb_intern_atom(ctx->connection, false, strlen(mime_type), mime_type); + + if (ctx->selection_data_source_send_fd < 0) { + 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)); + + request->fd = fd; + request->cookie = cookie; + request->data_source = host; + wl_list_insert(&ctx->selection_data_source_send_pending, &request->link); + } +} + +static void sl_internal_data_source_cancelled( + void* data, struct wl_data_source* data_source) { + struct sl_data_source* host = data; + + if (host->ctx->selection_data_source == host) + host->ctx->selection_data_source = NULL; + + wl_data_source_destroy(data_source); +} + +static const struct wl_data_source_listener sl_internal_data_source_listener = { + sl_internal_data_source_target, sl_internal_data_source_send, + sl_internal_data_source_cancelled}; + +char* sl_copy_atom_name(xcb_get_atom_name_reply_t* reply) { + // The string produced by xcb_get_atom_name_name isn't null terminated, so we + // have to copy |name_len| bytes into a new buffer and add the null character + // 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); + memcpy(name, name_start, name_len); + name[name_len] = '\0'; + return name; +} + +static void sl_get_selection_targets(struct sl_context* ctx) { + struct sl_data_source* data_source = NULL; + xcb_get_property_reply_t *reply; + xcb_atom_t *value; + uint32_t i; + + reply = xcb_get_property_reply( + ctx->connection, + xcb_get_property(ctx->connection, 1, ctx->selection_window, + ctx->atoms[ATOM_WL_SELECTION].value, + XCB_GET_PROPERTY_TYPE_ANY, 0, 4096), + NULL); + if (!reply) + return; + + if (reply->type != XCB_ATOM_ATOM) { + free(reply); + return; + } + + if (ctx->data_device_manager) { + data_source = malloc(sizeof(*data_source)); + assert(data_source); + + data_source->ctx = ctx; + data_source->internal = wl_data_device_manager_create_data_source( + ctx->data_device_manager->internal); + wl_data_source_add_listener(data_source->internal, + &sl_internal_data_source_listener, data_source); + + value = 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 + // round trip to the X server, but none of the requests depend on each + // other. Therefore, we can speed things up by sending out all the requests + // 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); + for (i = 0; i < reply->value_len; i++) { + atom_name_cookies[i] = xcb_get_atom_name(ctx->connection, value[i]); + } + for (i = 0; i < reply->value_len; i++) { + xcb_get_atom_name_reply_t* atom_name_reply = + xcb_get_atom_name_reply(ctx->connection, atom_name_cookies[i], NULL); + if (atom_name_reply) { + char* name = sl_copy_atom_name(atom_name_reply); + wl_data_source_offer(data_source->internal, name); + free(atom_name_reply); + free(name); + } + } + free(atom_name_cookies); + + if (ctx->selection_data_device && ctx->default_seat) { + wl_data_device_set_selection(ctx->selection_data_device, + data_source->internal, + ctx->default_seat->seat->last_serial); + } + + if (ctx->selection_data_source) { + wl_data_source_destroy(ctx->selection_data_source->internal); + free(ctx->selection_data_source); + } + ctx->selection_data_source = data_source; + } + + free(reply); +} + +static void sl_get_selection_data(struct sl_context* ctx) { + xcb_get_property_reply_t* reply = xcb_get_property_reply( + ctx->connection, + xcb_get_property(ctx->connection, 1, ctx->selection_window, + ctx->atoms[ATOM_WL_SELECTION].value, + XCB_GET_PROPERTY_TYPE_ANY, 0, 0x1fffffff), + NULL); + if (!reply) + return; + + if (reply->type == ctx->atoms[ATOM_INCR].value) { + ctx->selection_incremental_transfer = 1; + free(reply); + } else { + ctx->selection_incremental_transfer = 0; + sl_write_selection_property(ctx, reply); + } +} + +static void sl_handle_selection_notify(struct sl_context* ctx, + xcb_selection_notify_event_t* event) { + if (event->property == XCB_ATOM_NONE) + return; + + if (event->target == ctx->atoms[ATOM_TARGETS].value) + sl_get_selection_targets(ctx); + else + sl_get_selection_data(ctx); +} + +static void sl_send_targets(struct sl_context* ctx) { + xcb_change_property( + ctx->connection, XCB_PROP_MODE_REPLACE, ctx->selection_request.requestor, + ctx->selection_request.property, XCB_ATOM_ATOM, 32, + ctx->selection_data_offer->atoms.size / sizeof(xcb_atom_t), + ctx->selection_data_offer->atoms.data); + + sl_send_selection_notify(ctx, ctx->selection_request.property); +} + +static void sl_send_timestamp(struct sl_context* ctx) { + xcb_change_property(ctx->connection, XCB_PROP_MODE_REPLACE, + ctx->selection_request.requestor, + ctx->selection_request.property, XCB_ATOM_INTEGER, 32, 1, + &ctx->selection_timestamp); + + sl_send_selection_notify(ctx, ctx->selection_request.property); +} + +static void sl_send_data(struct sl_context* ctx, xcb_atom_t data_type) { + int rv, fd_to_receive, fd_to_wayland; + + if (!ctx->selection_data_offer) { + sl_send_selection_notify(ctx, XCB_ATOM_NONE); + return; + } + + if (ctx->selection_event_source) { + fprintf(stderr, "error: selection transfer already pending\n"); + sl_send_selection_notify(ctx, XCB_ATOM_NONE); + return; + } + + ctx->selection_data_type = data_type; + + // We will need the name of this atom later to tell the wayland server what + // type of data to send us, so start the request now. + xcb_get_atom_name_cookie_t atom_name_cookie = + xcb_get_atom_name(ctx->connection, 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, + }; + + 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; + } + + fd_to_receive = new_pipe.fd; + fd_to_wayland = new_pipe.fd; + + } 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; + } + + xcb_get_atom_name_reply_t* atom_name_reply = + xcb_get_atom_name_reply(ctx->connection, atom_name_cookie, NULL); + if (atom_name_reply) { + // If we got the atom name, then send the request to wayland and add our end + // of the pipe to the wayland event loop. + ctx->selection_data_offer_receive_fd = fd_to_receive; + char* name = sl_copy_atom_name(atom_name_reply); + wl_data_offer_receive(ctx->selection_data_offer->internal, name, + fd_to_wayland); + free(atom_name_reply); + free(name); + + ctx->selection_event_source = 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); + } else { + // If getting the atom name failed, notify the requestor that there won't be + // any data, and close our end of the pipe. + close(fd_to_receive); + sl_send_selection_notify(ctx, XCB_ATOM_NONE); + } + + // Close the wayland end of the pipe, now that it's either been sent or not + // going to be sent. The VIRTWL driver uses the same fd for both ends of the + // pipe, so don't close the fd if both ends are the same. + if (fd_to_receive != fd_to_wayland) + close(fd_to_wayland); +} + +static void sl_handle_selection_request(struct sl_context* ctx, + xcb_selection_request_event_t* event) { + ctx->selection_request = *event; + ctx->selection_incremental_transfer = 0; + + if (event->selection == ctx->atoms[ATOM_CLIPBOARD_MANAGER].value) { + sl_send_selection_notify(ctx, ctx->selection_request.property); + return; + } + + if (event->target == ctx->atoms[ATOM_TARGETS].value) { + sl_send_targets(ctx); + } else if (event->target == ctx->atoms[ATOM_TIMESTAMP].value) { + sl_send_timestamp(ctx); + } else { + int success = 0; + xcb_atom_t* atom; + wl_array_for_each(atom, &ctx->selection_data_offer->atoms) { + if (event->target == *atom) { + success = 1; + sl_send_data(ctx, *atom); + break; + } + } + if (!success) { + sl_send_selection_notify(ctx, XCB_ATOM_NONE); + } + } +} + +static void sl_handle_xfixes_selection_notify( + struct sl_context* ctx, xcb_xfixes_selection_notify_event_t* event) { + if (event->selection != ctx->atoms[ATOM_CLIPBOARD].value) + return; + + if (event->owner == XCB_WINDOW_NONE) { + // If client selection is gone. Set NULL selection for each seat. + if (ctx->selection_owner != ctx->selection_window) { + if (ctx->selection_data_device && ctx->default_seat) { + wl_data_device_set_selection(ctx->selection_data_device, NULL, + ctx->default_seat->seat->last_serial); + } + } + ctx->selection_owner = XCB_WINDOW_NONE; + return; + } + + ctx->selection_owner = event->owner; + + // Save timestamp if it's our selection. + if (event->owner == ctx->selection_window) { + ctx->selection_timestamp = event->timestamp; + return; + } + + ctx->selection_incremental_transfer = 0; + xcb_convert_selection(ctx->connection, ctx->selection_window, + ctx->atoms[ATOM_CLIPBOARD].value, + ctx->atoms[ATOM_TARGETS].value, + ctx->atoms[ATOM_WL_SELECTION].value, event->timestamp); +} + +static int sl_handle_x_connection_event(int fd, uint32_t mask, void* data) { + 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; + + 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); + break; + case XCB_DESTROY_NOTIFY: + sl_handle_destroy_notify(ctx, (xcb_destroy_notify_event_t*)event); + break; + case XCB_REPARENT_NOTIFY: + sl_handle_reparent_notify(ctx, (xcb_reparent_notify_event_t*)event); + break; + case XCB_MAP_REQUEST: + sl_handle_map_request(ctx, (xcb_map_request_event_t*)event); + break; + case XCB_MAP_NOTIFY: + sl_handle_map_notify(ctx, (xcb_map_notify_event_t*)event); + break; + case XCB_UNMAP_NOTIFY: + sl_handle_unmap_notify(ctx, (xcb_unmap_notify_event_t*)event); + break; + case XCB_CONFIGURE_REQUEST: + sl_handle_configure_request(ctx, (xcb_configure_request_event_t*)event); + break; + case XCB_CONFIGURE_NOTIFY: + sl_handle_configure_notify(ctx, (xcb_configure_notify_event_t*)event); + break; + case XCB_CLIENT_MESSAGE: + sl_handle_client_message(ctx, (xcb_client_message_event_t*)event); + break; + case XCB_FOCUS_IN: + sl_handle_focus_in(ctx, (xcb_focus_in_event_t*)event); + break; + case XCB_FOCUS_OUT: + sl_handle_focus_out(ctx, (xcb_focus_out_event_t*)event); + break; + case XCB_PROPERTY_NOTIFY: + sl_handle_property_notify(ctx, (xcb_property_notify_event_t*)event); + break; + case XCB_SELECTION_NOTIFY: + sl_handle_selection_notify(ctx, (xcb_selection_notify_event_t*)event); + break; + case XCB_SELECTION_REQUEST: + sl_handle_selection_request(ctx, (xcb_selection_request_event_t*)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); + break; + } + + free(event); + ++count; + } + + if ((mask & ~WL_EVENT_WRITABLE) == 0) + xcb_flush(ctx->connection); + + return count; +} + +static void sl_connect(struct sl_context* ctx) { + const char wm_name[] = "Sommelier"; + const xcb_setup_t *setup; + xcb_screen_iterator_t screen_iterator; + uint32_t values[1]; + xcb_void_cookie_t change_attributes_cookie, redirect_subwindows_cookie; + xcb_generic_error_t *error; + xcb_intern_atom_reply_t *atom_reply; + xcb_depth_iterator_t depth_iterator; + xcb_xfixes_query_version_reply_t *xfixes_query_version_reply; + const xcb_query_extension_reply_t *composite_extension; + unsigned i; + + ctx->connection = xcb_connect_to_fd(ctx->wm_fd, NULL); + assert(!xcb_connection_has_error(ctx->connection)); + + xcb_prefetch_extension_data(ctx->connection, &xcb_xfixes_id); + xcb_prefetch_extension_data(ctx->connection, &xcb_composite_id); + + 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); + } + + setup = xcb_get_setup(ctx->connection); + screen_iterator = xcb_setup_roots_iterator(setup); + ctx->screen = screen_iterator.data; + + // Select for substructure redirect. + values[0] = XCB_EVENT_MASK_STRUCTURE_NOTIFY | + XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | + XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT; + 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( + wl_display_get_event_loop(ctx->host_display), + xcb_get_file_descriptor(ctx->connection), WL_EVENT_READABLE, + &sl_handle_x_connection_event, ctx); + + ctx->xfixes_extension = + xcb_get_extension_data(ctx->connection, &xcb_xfixes_id); + assert(ctx->xfixes_extension->present); + + xfixes_query_version_reply = xcb_xfixes_query_version_reply( + ctx->connection, + xcb_xfixes_query_version(ctx->connection, XCB_XFIXES_MAJOR_VERSION, + XCB_XFIXES_MINOR_VERSION), + NULL); + assert(xfixes_query_version_reply); + assert(xfixes_query_version_reply->major_version >= 5); + free(xfixes_query_version_reply); + + composite_extension = + xcb_get_extension_data(ctx->connection, &xcb_composite_id); + assert(composite_extension->present); + UNUSED(composite_extension); + + redirect_subwindows_cookie = xcb_composite_redirect_subwindows_checked( + ctx->connection, ctx->screen->root, XCB_COMPOSITE_REDIRECT_MANUAL); + + // Another window manager should not be running. + error = xcb_request_check(ctx->connection, change_attributes_cookie); + assert(!error); + + // Redirecting subwindows of root for compositing should have succeeded. + error = xcb_request_check(ctx->connection, redirect_subwindows_cookie); + assert(!error); + + ctx->window = xcb_generate_id(ctx->connection); + xcb_create_window(ctx->connection, 0, ctx->window, ctx->screen->root, 0, 0, 1, + 1, 0, XCB_WINDOW_CLASS_INPUT_ONLY, XCB_COPY_FROM_PARENT, 0, + NULL); + + for (i = 0; i < ARRAY_SIZE(ctx->atoms); ++i) { + atom_reply = + xcb_intern_atom_reply(ctx->connection, ctx->atoms[i].cookie, &error); + assert(!error); + ctx->atoms[i].value = atom_reply->atom; + free(atom_reply); + } + + depth_iterator = xcb_screen_allowed_depths_iterator(ctx->screen); + while (depth_iterator.rem > 0) { + int depth = depth_iterator.data->depth; + if (depth == ctx->screen->root_depth) { + ctx->visual_ids[depth] = ctx->screen->root_visual; + ctx->colormaps[depth] = ctx->screen->default_colormap; + } else { + xcb_visualtype_iterator_t visualtype_iterator = + xcb_depth_visuals_iterator(depth_iterator.data); + + ctx->visual_ids[depth] = visualtype_iterator.data->visual_id; + ctx->colormaps[depth] = xcb_generate_id(ctx->connection); + xcb_create_colormap(ctx->connection, XCB_COLORMAP_ALLOC_NONE, + ctx->colormaps[depth], ctx->screen->root, + ctx->visual_ids[depth]); + } + xcb_depth_next(&depth_iterator); + } + assert(ctx->visual_ids[ctx->screen->root_depth]); + + if (ctx->clipboard_manager) { + values[0] = XCB_EVENT_MASK_PROPERTY_CHANGE; + ctx->selection_window = xcb_generate_id(ctx->connection); + xcb_create_window(ctx->connection, XCB_COPY_FROM_PARENT, + ctx->selection_window, ctx->screen->root, 0, 0, 1, 1, 0, + XCB_WINDOW_CLASS_INPUT_OUTPUT, ctx->screen->root_visual, + XCB_CW_EVENT_MASK, values); + xcb_set_selection_owner(ctx->connection, ctx->selection_window, + ctx->atoms[ATOM_CLIPBOARD_MANAGER].value, + XCB_CURRENT_TIME); + xcb_xfixes_select_selection_input( + ctx->connection, ctx->selection_window, + ctx->atoms[ATOM_CLIPBOARD].value, + XCB_XFIXES_SELECTION_EVENT_MASK_SET_SELECTION_OWNER | + XCB_XFIXES_SELECTION_EVENT_MASK_SELECTION_WINDOW_DESTROY | + XCB_XFIXES_SELECTION_EVENT_MASK_SELECTION_CLIENT_CLOSE); + sl_set_selection(ctx, NULL); + } + + xcb_change_property(ctx->connection, XCB_PROP_MODE_REPLACE, ctx->window, + ctx->atoms[ATOM_NET_SUPPORTING_WM_CHECK].value, + XCB_ATOM_WINDOW, 32, 1, &ctx->window); + xcb_change_property(ctx->connection, XCB_PROP_MODE_REPLACE, ctx->window, + ctx->atoms[ATOM_NET_WM_NAME].value, + ctx->atoms[ATOM_UTF8_STRING].value, 8, strlen(wm_name), + wm_name); + 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); + 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); +} + +static void sl_sd_notify(const char* state) { + const char* socket_name; + struct msghdr msghdr; + struct iovec iovec; + struct sockaddr_un addr; + int fd; + int rv; + + socket_name = getenv("NOTIFY_SOCKET"); + assert(socket_name); + + fd = socket(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0); + 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.iov_len = strlen(state); + + memset(&msghdr, 0, sizeof(msghdr)); + msghdr.msg_name = &addr; + msghdr.msg_namelen = + offsetof(struct sockaddr_un, sun_path) + strlen(socket_name); + msghdr.msg_iov = &iovec; + msghdr.msg_iovlen = 1; + + rv = sendmsg(fd, &msghdr, MSG_NOSIGNAL); + assert(rv != -1); + UNUSED(rv); +} + +static int sl_handle_sigchld(int signal_number, void* data) { + struct sl_context* ctx = (struct sl_context*)data; + int status; + pid_t pid; + + while ((pid = waitpid(-1, &status, WNOHANG)) > 0) { + if (pid == ctx->child_pid) { + ctx->child_pid = -1; + if (WIFEXITED(status) && WEXITSTATUS(status)) { + fprintf(stderr, "Child exited with status: %d\n", WEXITSTATUS(status)); + } + if (ctx->exit_with_child) { + if (ctx->xwayland_pid >= 0) + kill(ctx->xwayland_pid, SIGTERM); + } else { + // Notify systemd that we are ready to accept connections now that + // child process has finished running and all environment is ready. + if (ctx->sd_notify) + sl_sd_notify(ctx->sd_notify); + } + } else if (pid == ctx->xwayland_pid) { + ctx->xwayland_pid = -1; + if (WIFEXITED(status) && WEXITSTATUS(status)) { + fprintf(stderr, "Xwayland exited with status: %d\n", + WEXITSTATUS(status)); + exit(WEXITSTATUS(status)); + } + } + } + + return 1; +} + +static void sl_execvp(const char* file, + char* const argv[], + int wayland_socked_fd) { + if (wayland_socked_fd >= 0) { + int fd; + fd = dup(wayland_socked_fd); + putenv(sl_xasprintf("WAYLAND_SOCKET=%d", fd)); + } + + setenv("SOMMELIER_VERSION", SOMMELIER_VERSION, 1); + + execvp(file, argv); + perror(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; + + // 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); + + default_scale_factor = device_scale_factor * preferred_scale; + } + break; + } + } + + // 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)); + + // Scale affects output state. Send updated output state to xwayland. + wl_list_for_each(output, &ctx->host_outputs, link) + sl_output_send_host_output_state(output); +} + +static int sl_handle_display_ready_event(int fd, uint32_t mask, void* data) { + 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; + + display_name[0] = ':'; + do { + int bytes_left = sizeof(display_name) - bytes_read - 1; + int bytes; + + if (!bytes_left) + break; + + bytes = read(fd, &display_name[bytes_read + 1], bytes_left); + if (!bytes) + break; + + bytes_read += bytes; + } while (display_name[bytes_read] != '\n'); + + display_name[bytes_read] = '\0'; + setenv("DISPLAY", display_name, 1); + + sl_connect(ctx); + + wl_event_source_remove(ctx->display_ready_event_source); + ctx->display_ready_event_source = NULL; + close(fd); + + // Calculate scale now that the default scale factor is known. This also + // happens to workaround an issue in Xwayland where an output update is + // needed for DPI to be set correctly. + sl_calculate_scale_for_xwayland(ctx); + wl_display_flush_clients(ctx->host_display); + + putenv(sl_xasprintf("XCURSOR_SIZE=%d", + (int)(XCURSOR_SIZE_BASE * ctx->scale + 0.5))); + + pid = fork(); + assert(pid >= 0); + if (pid == 0) { + sl_execvp(ctx->runprog[0], ctx->runprog, -1); + _exit(EXIT_FAILURE); + } + + ctx->child_pid = pid; + + return 1; +} + +static void sl_sigchld_handler(int signal) { + while (waitpid(-1, NULL, WNOHANG) > 0) + continue; +} + +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) { + char *s = str; + int n = 0; + int delim = 0; + + do { + // Look for ending delimiter if |delim| is set. + if (delim) { + if (*s == delim) { + delim = 0; + *s = '\0'; + } + ++s; + } else { + // Skip forward to first non-space character. + while (*s == ' ' && *s != '\0') + ++s; + + // Check for quote delimiter. + if (*s == '"') { + delim = '"'; + ++s; + } else { + delim = ' '; + } + + // Add string to arguments if there's room. + if (n < argc) + argv[n] = s; + + ++n; + } + } while (*s != '\0'); + + return n; +} + +static void sl_print_usage() { + printf( + "usage: sommelier [options] [program] [args...]\n\n" + "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" + " --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" + " --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" + " --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"); +} + +static const char* sl_arg_value(const char* arg) { + const char* s = strchr(arg, '='); + if (!s) { + sl_print_usage(); + exit(EXIT_FAILURE); + } + 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}}; + 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 *glamor = getenv("SOMMELIER_GLAMOR"); + const char *shm_driver = getenv("SOMMELIER_SHM_DRIVER"); + const char *data_driver = getenv("SOMMELIER_DATA_DRIVER"); + 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 *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 *socket_name = "wayland-0"; + const char *runtime_dir; + struct wl_event_loop *event_loop; + struct wl_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 client_fd = -1; + int rv; + int i; + + for (i = 1; i < argc; ++i) { + const char *arg = argv[i]; + if (strcmp(arg, "--help") == 0 || strcmp(arg, "-h") == 0 || + strcmp(arg, "-?") == 0) { + sl_print_usage(); + return EXIT_SUCCESS; + } + if (strcmp(arg, "--version") == 0 || strcmp(arg, "-v") == 0) { + printf("Version: %s\n", SOMMELIER_VERSION); + return EXIT_SUCCESS; + } + if (strstr(arg, "--master") == arg) { + master = 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) { + peer_cmd_prefix = sl_arg_value(arg); + } else if (strstr(arg, "--xwayland-cmd-prefix") == arg) { + 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, "--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, "--application-id") == arg) { + ctx.application_id = sl_arg_value(arg); + } else if (strstr(arg, "-X") == arg) { + ctx.xwayland = 1; + } else if (strstr(arg, "--x-display") == arg) { + xdisplay = atoi(sl_arg_value(arg)); + // Automatically enable X forwarding if X display is specified. + ctx.xwayland = 1; + } else if (strstr(arg, "--xwayland-path") == arg) { + xwayland_path = sl_arg_value(arg); + } else if (strstr(arg, "--xwayland-gl-driver-path") == arg) { + xwayland_gl_driver_path = sl_arg_value(arg); + } else if (strstr(arg, "--no-exit-with-child") == arg) { + ctx.exit_with_child = 0; + } else if (strstr(arg, "--sd-notify") == arg) { + ctx.sd_notify = sl_arg_value(arg); + } else if (strstr(arg, "--no-clipboard-manager") == arg) { + clipboard_manager = "0"; + } else if (strstr(arg, "--frame-color") == arg) { + 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, "--glamor") == arg) { + glamor = "1"; + } 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 (arg[0] == '-') { + if (strcmp(arg, "--") == 0) { + ctx.runprog = &argv[i + 1]; + break; + } + // Don't exit on unknown options so we can have forward compatibility + // with new flags introduced. + fprintf(stderr, "Option `%s' is unknown, ignoring.\n", arg); + } else { + ctx.runprog = &argv[i]; + break; + } + } + + 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 (master) { + char* lock_addr; + struct sockaddr_un addr; + struct sigaction sa; + struct stat sock_stat; + int lock_fd; + int sock_fd; + + 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; + + /* + fprintf(stderr, "peer_cmd_prefix = '%s'\n", 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 (client_fd == -1) { + if (!ctx.runprog || !ctx.runprog[0]) { + sl_print_usage(); + return EXIT_FAILURE; + } + } + + if (ctx.xwayland) { + assert(client_fd == -1); + + ctx.clipboard_manager = 1; + if (clipboard_manager) + ctx.clipboard_manager = !!strcmp(clipboard_manager, "0"); + } + + 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))); + } + + if (!frame_color) + frame_color = FRAME_COLOR; + + if (frame_color) { + int r, g, b; + if (sscanf(frame_color, "#%02x%02x%02x", &r, &g, &b) == 3) + ctx.frame_color = 0xff000000 | (r << 16) | (g << 8) | (b << 0); + } + + if (!dark_frame_color) + dark_frame_color = DARK_FRAME_COLOR; + + if (dark_frame_color) { + int r, g, b; + if (sscanf(dark_frame_color, "#%02x%02x%02x", &r, &g, &b) == 3) + ctx.dark_frame_color = 0xff000000 | (r << 16) | (g << 8) | (b << 0); + } + + // 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; + } + //fprintf(stderr, "ioctl(%d, VIRTWL_IOCTL_NEW_CTX) -> %d\n", ctx.virtwl_fd, new_ctx.fd); + + 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 (drm_device) { + int drm_fd = open(drm_device, O_RDWR | O_CLOEXEC); + if (drm_fd == -1) { + fprintf(stderr, "error: could not open %s (%s)\n", drm_device, + strerror(errno)); + return EXIT_FAILURE; + } + + ctx.gbm = gbm_create_device(drm_fd); + if (!ctx.gbm) { + fprintf(stderr, "error: couldn't get display device\n"); + return EXIT_FAILURE; + } + + 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, ","); + int* p; + + while (token) { + p = wl_array_add(&ctx.dpi, sizeof *p); + assert(p); + *p = MAX(MIN_DPI, MIN(atoi(token), MAX_DPI)); + token = strtok(NULL, ","); + } + 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); + 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); + } else { + if (display == NULL) + display = getenv("WAYLAND_DISPLAY"); + if (display == NULL) + display = "wayland-0"; + + ctx.display = wl_display_connect(display); + } + + if (!ctx.display) { + fprintf(stderr, "error: failed to connect to %s\n", display); + 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); + + // 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 = + wl_event_loop_add_fd(event_loop, wl_display_get_fd(ctx.display), + WL_EVENT_READABLE, sl_handle_event, &ctx); + + wl_registry_add_listener(wl_display_get_registry(ctx.display), + &sl_registry_listener, &ctx); + + 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); + + 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); + + 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; + } else { + pid = fork(); + assert(pid != -1); + if (pid == 0) { + sl_execvp(ctx.runprog[0], ctx.runprog, sv[1]); + _exit(EXIT_FAILURE); + } + ctx.child_pid = pid; + } + close(sv[1]); + } + + wl_client_add_destroy_listener(ctx.client, &client_destroy_listener); + + do { + wl_display_flush_clients(ctx.host_display); + if (ctx.connection) { + if (ctx.needs_set_input_focus) { + sl_set_input_focus(&ctx, ctx.host_focus_window); + ctx.needs_set_input_focus = 0; + } + xcb_flush(ctx.connection); + } + if (wl_display_flush(ctx.display) < 0) + return EXIT_FAILURE; + } while (wl_event_loop_dispatch(event_loop, -1) != -1); + + return EXIT_SUCCESS; +} diff --git a/sommelier/sommelier.h b/sommelier/sommelier.h new file mode 100644 index 0000000..a512bfa --- /dev/null +++ b/sommelier/sommelier.h @@ -0,0 +1,547 @@ +// 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. + +#ifndef VM_TOOLS_SOMMELIER_SOMMELIER_H_ +#define VM_TOOLS_SOMMELIER_SOMMELIER_H_ + +#include +#include +#include +#include +#include + +#include "config.h" + +#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; +struct sl_shell; +struct sl_data_device_manager; +struct sl_data_offer; +struct sl_data_source; +struct sl_xdg_shell; +struct sl_subcompositor; +struct sl_aura_shell; +struct sl_viewporter; +struct sl_linux_dmabuf; +struct sl_keyboard_extension; +struct sl_text_input_manager; +struct sl_relative_pointer_manager; +struct sl_window; +struct zaura_shell; +struct zcr_keyboard_extension_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, +}; + +enum { + SHM_DRIVER_NOOP, + SHM_DRIVER_DMABUF, + SHM_DRIVER_VIRTWL, + SHM_DRIVER_VIRTWL_DMABUF, +}; + +enum { + DATA_DRIVER_NOOP, + DATA_DRIVER_VIRTWL, +}; + +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 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]; +}; + +struct sl_compositor { + struct sl_context* ctx; + uint32_t id; + uint32_t version; + struct sl_global* host_global; + struct wl_compositor* internal; +}; + +struct sl_shm { + struct sl_context* ctx; + uint32_t id; + struct sl_global* host_global; + struct wl_shm* internal; +}; + +struct sl_seat { + struct sl_context* ctx; + uint32_t id; + uint32_t version; + struct sl_global* host_global; + uint32_t last_serial; + struct wl_list link; +}; + +struct sl_host_pointer { + struct sl_seat* seat; + struct wl_resource* resource; + struct wl_pointer* proxy; + struct wl_resource* focus_resource; + struct wl_listener focus_resource_listener; + uint32_t focus_serial; +}; + +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 { + struct wl_list link; + wl_fixed_t src_x; + wl_fixed_t src_y; + wl_fixed_t src_width; + wl_fixed_t src_height; + int32_t dst_width; + int32_t dst_height; +}; + +struct sl_host_callback { + struct wl_resource* resource; + struct wl_callback* proxy; +}; + +struct sl_host_surface { + struct sl_context* ctx; + struct wl_resource* resource; + struct wl_surface* proxy; + struct wp_viewport* viewport; + uint32_t contents_width; + uint32_t contents_height; + int32_t contents_scale; + struct wl_list contents_viewport; + struct sl_mmap* contents_shm_mmap; + int has_role; + int has_output; + uint32_t last_event_serial; + struct sl_output_buffer* current_buffer; + struct wl_list released_buffers; + struct wl_list busy_buffers; +}; + +struct sl_host_buffer { + struct wl_resource* resource; + struct wl_buffer* proxy; + uint32_t width; + uint32_t height; + struct sl_mmap* shm_mmap; + uint32_t shm_format; + struct sl_sync_point* sync_point; +}; + +struct sl_data_source_send_request { + int fd; + xcb_intern_atom_cookie_t cookie; + struct sl_data_source* data_source; + struct wl_list link; +}; + +struct sl_subcompositor { + struct sl_context* ctx; + uint32_t id; + struct sl_global* host_global; +}; + +struct sl_shell { + struct sl_context* ctx; + uint32_t id; + struct sl_global* host_global; +}; + +struct sl_output { + struct sl_context* ctx; + uint32_t id; + uint32_t version; + struct sl_global* host_global; + struct wl_list link; +}; + +struct sl_host_output { + struct sl_context* ctx; + struct wl_resource* resource; + struct wl_output* proxy; + struct zaura_output* aura_output; + int internal; + int x; + int y; + int physical_width; + int physical_height; + int subpixel; + char* make; + char* model; + int transform; + uint32_t flags; + int width; + int height; + int refresh; + int scale_factor; + int current_scale; + int preferred_scale; + int device_scale_factor; + int expecting_scale; + struct wl_list link; +}; + +struct sl_host_seat { + struct sl_seat* seat; + struct wl_resource* resource; + struct wl_seat* proxy; +}; + +struct sl_accelerator { + struct wl_list link; + uint32_t modifiers; + xkb_keysym_t symbol; +}; + +struct sl_keyboard_extension { + struct sl_context* ctx; + uint32_t id; + struct zcr_keyboard_extension_v1* internal; +}; + +struct sl_data_device_manager { + struct sl_context* ctx; + uint32_t id; + uint32_t version; + struct sl_global* host_global; + struct wl_data_device_manager* internal; +}; + +struct sl_data_offer { + struct sl_context* ctx; + struct wl_data_offer* internal; + struct wl_array atoms; // Contains xcb_atom_t + struct wl_array cookies; // Contains xcb_intern_atom_cookie_t +}; + +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_viewporter { + struct sl_context* ctx; + uint32_t id; + struct sl_global* host_viewporter_global; + struct wp_viewporter* internal; +}; + +struct sl_xdg_shell { + struct sl_context* ctx; + uint32_t id; + struct sl_global* host_global; + struct zxdg_shell_v6* internal; +}; + +struct sl_aura_shell { + struct sl_context* ctx; + uint32_t id; + uint32_t version; + struct sl_global* host_gtk_shell_global; + struct zaura_shell* internal; +}; + +struct sl_linux_dmabuf { + struct sl_context* ctx; + uint32_t id; + uint32_t version; + struct sl_global* host_drm_global; + struct zwp_linux_dmabuf_v1* internal; +}; + +struct sl_global { + struct sl_context* ctx; + const struct wl_interface* interface; + uint32_t name; + uint32_t version; + void* data; + wl_global_bind_func_t bind; + struct wl_list link; +}; + +struct sl_host_registry { + struct sl_context* ctx; + struct wl_resource* resource; + 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); + +struct sl_sync_point { + int fd; + 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 { + 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 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; + struct wl_list link; +}; + +struct sl_host_buffer* sl_create_host_buffer(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); + +struct sl_global* sl_compositor_global_create(struct sl_context* ctx); + +size_t sl_shm_bpp_for_shm_format(uint32_t format); + +size_t sl_shm_num_planes_for_shm_format(uint32_t format); + +struct sl_global* sl_shm_global_create(struct sl_context* ctx); + +struct sl_global* sl_subcompositor_global_create(struct sl_context* ctx); + +struct sl_global* sl_shell_global_create(struct sl_context* ctx); + +double sl_output_aura_scale_factor_to_double(int scale_factor); + +void sl_output_send_host_output_state(struct sl_host_output* host); + +struct sl_global* sl_output_global_create(struct sl_output* output); + +struct sl_global* sl_seat_global_create(struct sl_seat* seat); + +struct sl_global* sl_relative_pointer_manager_global_create( + struct sl_context* ctx); + +struct sl_global* sl_data_device_manager_global_create(struct sl_context* ctx); + +struct sl_global* sl_viewporter_global_create(struct sl_context* ctx); + +struct sl_global* sl_xdg_shell_global_create(struct sl_context* ctx); + +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_manager_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); + +struct sl_sync_point* sl_sync_point_create(int fd); +void sl_sync_point_destroy(struct sl_sync_point* sync_point); + +void sl_host_seat_added(struct sl_host_seat* host); +void sl_host_seat_removed(struct sl_host_seat* host); + +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); + +#endif // VM_TOOLS_SOMMELIER_SOMMELIER_H_ diff --git a/sommelier/wayland-protocol.gypi b/sommelier/wayland-protocol.gypi new file mode 100644 index 0000000..18b6d4c --- /dev/null +++ b/sommelier/wayland-protocol.gypi @@ -0,0 +1,34 @@ +# 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 new file mode 100644 index 0000000..4f18a0c --- /dev/null +++ b/sommelier/wayland_protocol.gni @@ -0,0 +1,65 @@ +# Copyright 2019 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. + +# GN template to generate static library for given Wayland protorol description files. +# Parameters: +# sources +# Wayland protocol description XML file paths. +# out_dir (optional) +# Directory to output generated source files. Relative to gen/ directory. +template("wayland_protocol_library") { + forward_variables_from(invoker, [ "out_dir" ]) + if (!defined(out_dir)) { + out_dir = "." + } + wayland_dir = "${root_gen_dir}/${out_dir}" + + generators = [ + { + subcommand = "code" + output_suffix = "-protocol.c" + }, + { + subcommand = "client-header" + output_suffix = "-client-protocol.h" + }, + { + subcommand = "server-header" + output_suffix = "-server-protocol.h" + }, + ] + generator_actions = [] + foreach(g, generators) { + action_name = "${target_name}_${g.subcommand}" + generator_actions += [ ":" + action_name ] + action_foreach(action_name) { + sources = invoker.sources + script = "//common-mk/file_generator_wrapper.py" + output_file = "${wayland_dir}/{{source_name_part}}${g.output_suffix}" + outputs = [ + output_file, + ] + args = [ + "wayland-scanner", + g.subcommand, + "{{source}}", + output_file, + ] + } + } + + static_library(target_name) { + if (defined(invoker.configs)) { + configs += invoker.configs + } + deps = generator_actions + sources = [] + foreach(t, deps) { + sources += get_target_outputs(t) + } + if (defined(invoker.deps)) { + deps += invoker.deps + } + } +}