Update sommelier to dbd90c6b002f7d0867cc0b0f1538cc979b688d13

This commit is contained in:
Bruce Leidl 2023-01-26 15:40:13 -05:00
parent 65052aac7a
commit 45bfd42fec
77 changed files with 14707 additions and 5937 deletions

View File

@ -1,3 +0,0 @@
*-protocol.h
*.o
sommelier

View File

@ -1,23 +1,18 @@
# Copyright 2019 The Chromium OS Authors. All rights reserved.
# Copyright 2019 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import("//common-mk/pkg_config.gni")
import("wayland_protocol.gni")
group("all") {
deps = [
":sommelier",
":wayland_demo",
":x11_demo",
]
}
if (!defined(peer_cmd_prefix)) {
if (use.amd64) {
peer_cmd_prefix = "\"/opt/google/cros-containers/lib/ld-linux-x86-64.so.2 --library-path /opt/google/cros-containers/lib --inhibit-rpath \\\"\\\"\""
deps = [ ":sommelier" ]
if (use.fuzzer) {
deps += [ ":sommelier_wayland_fuzzer" ]
}
if (use.arm) {
peer_cmd_prefix = "\"/opt/google/cros-containers/lib/ld-linux-armhf.so.3 --library-path /opt/google/cros-containers/lib --inhibit-rpath \\\"\\\"\""
if (use.test) {
deps += [ ":sommelier_test" ]
}
}
@ -31,21 +26,6 @@ if (!defined(xwayland_gl_driver_path)) {
xwayland_gl_driver_path = "\"/opt/google/cros-containers/lib\""
}
# Set this to the shm driver to use for Xwayland.
if (!defined(xwayland_shm_driver)) {
xwayland_shm_driver = "\"virtwl\""
}
# Set this to the shm driver to use for wayland clients.
if (!defined(shm_driver)) {
shm_driver = "\"virtwl-dmabuf\""
}
# Set this to the virtwl device.
if (!defined(virtwl_device)) {
virtwl_device = "\"/dev/wl0\""
}
# Set this to the frame color to use for Xwayland clients.
if (!defined(frame_color)) {
frame_color = "\"#f2f2f2\""
@ -61,87 +41,131 @@ wayland_protocol_library("sommelier-protocol") {
sources = [
"protocol/aura-shell.xml",
"protocol/drm.xml",
"protocol/gaming-input-unstable-v2.xml",
"protocol/gtk-shell.xml",
"protocol/keyboard-extension-unstable-v1.xml",
"protocol/linux-dmabuf-unstable-v1.xml",
"protocol/linux-explicit-synchronization-unstable-v1.xml",
"protocol/pointer-constraints-unstable-v1.xml",
"protocol/relative-pointer-unstable-v1.xml",
"protocol/text-input-extension-unstable-v1.xml",
"protocol/text-input-unstable-v1.xml",
"protocol/text-input-x11-unstable-v1.xml",
"protocol/viewporter.xml",
"protocol/xdg-shell-unstable-v6.xml",
"protocol/xdg-output-unstable-v1.xml",
"protocol/xdg-shell.xml",
]
}
executable("sommelier") {
pkg_deps = [
"gbm",
"grpc++",
"libdrm",
"pixman-1",
"protobuf",
"vm_protos",
"wayland-client",
"wayland-server",
"xcb",
"xcb-composite",
"xcb-xfixes",
"xkbcommon",
]
libs = [ "m" ]
deps = [
":sommelier-protocol",
]
sources = [
"sommelier-compositor.c",
"sommelier-data-device-manager.c",
"sommelier-display.c",
"sommelier-drm.c",
"sommelier-gtk-shell.c",
"sommelier-output.c",
"sommelier-pointer-constraints.c",
"sommelier-relative-pointer-manager.c",
"sommelier-seat.c",
"sommelier-shell.c",
"sommelier-shm.c",
"sommelier-subcompositor.c",
"sommelier-text-input.c",
"sommelier-viewporter.c",
"sommelier-xdg-shell.c",
"sommelier.c",
]
defines = [
gaming_defines = [ "GAMEPAD_SUPPORT" ]
gaming_deps = [ ":libgaming" ]
tracing_defines = [ "PERFETTO_TRACING" ]
tracing_pkg_deps = [ "perfetto" ]
tracing_libs = [ "pthread" ]
sommelier_defines = [
"_GNU_SOURCE",
"WL_HIDE_DEPRECATED",
"XWAYLAND_PATH=${xwayland_path}",
"XWAYLAND_GL_DRIVER_PATH=${xwayland_gl_driver_path}",
"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}",
] + gaming_defines + tracing_defines
static_library("libgaming") {
sources = [ "sommelier-gaming.cc" ]
defines = gaming_defines
pkg_deps = [
"libevdev",
"pixman-1",
]
deps = [ ":sommelier-protocol" ]
}
executable("wayland_demo") {
static_library("libsommelier") {
sources = [
"compositor/sommelier-compositor.cc",
"compositor/sommelier-drm.cc",
"compositor/sommelier-mmap.cc",
"compositor/sommelier-shm.cc",
"sommelier-ctx.cc",
"sommelier-data-device-manager.cc",
"sommelier-display.cc",
"sommelier-global.cc",
"sommelier-gtk-shell.cc",
"sommelier-output.cc",
"sommelier-pointer-constraints.cc",
"sommelier-relative-pointer-manager.cc",
"sommelier-seat.cc",
"sommelier-shell.cc",
"sommelier-subcompositor.cc",
"sommelier-text-input.cc",
"sommelier-timing.cc",
"sommelier-tracing.cc",
"sommelier-transform.cc",
"sommelier-util.cc",
"sommelier-viewporter.cc",
"sommelier-window.cc",
"sommelier-xdg-shell.cc",
"sommelier-xshape.cc",
"sommelier.cc",
"virtualization/virtgpu_channel.cc",
"virtualization/virtwl_channel.cc",
]
include_dirs = []
defines = sommelier_defines
pkg_deps = [
"libbrillo-${libbase_ver}",
"libchrome-${libbase_ver}",
"gbm",
"libdrm",
"pixman-1",
"wayland-client",
]
libs = [ "wayland-client" ]
sources = [
"demos/wayland_demo.cc",
]
"wayland-server",
"xcb",
"xcb-composite",
"xcb-shape",
"xcb-xfixes",
"xkbcommon",
] + tracing_pkg_deps
libs = [ "m" ] + tracing_libs
deps = [ ":sommelier-protocol" ] + gaming_deps
}
executable("x11_demo") {
pkg_deps = [
"libbrillo-${libbase_ver}",
"libchrome-${libbase_ver}",
]
libs = [ "X11" ]
sources = [
"demos/x11_demo.cc",
]
executable("sommelier") {
sources = [ "sommelier-main.cc" ]
defines = sommelier_defines
deps = [ ":libsommelier" ]
}
if (use.test) {
executable("sommelier_test") {
sources = [ "sommelier_test.cc" ]
pkg_deps = [ "pixman-1" ]
# gnlint: disable=GnLintCommonTesting
libs = [
"gmock",
"gtest",
"pixman-1",
]
defines = sommelier_defines
deps = [ ":libsommelier" ]
}
}
if (use.fuzzer) {
executable("sommelier_wayland_fuzzer") {
sources = [ "sommelier-wayland-fuzzer.cc" ]
pkg_deps = [ "pixman-1" ]
libs = [ "pixman-1" ]
configs += [ "//common-mk/common_fuzzer" ]
defines = sommelier_defines
deps = [ ":libsommelier" ]
}
}

View File

@ -1,129 +0,0 @@
CC=gcc
SED=sed
CLANG_FORMAT=clang-format-3.9
CLANG_TIDY=clang-tidy-3.9
PREFIX = /usr
SYSCONFDIR = /etc
BINDIR = $(PREFIX)/bin
SRCFILES := sommelier.c version.h
XMLFILES := protocol/aura-shell.xml protocol/viewporter.xml protocol/xdg-shell-unstable-v6.xml protocol/linux-dmabuf-unstable-v1.xml protocol/drm.xml protocol/keyboard-extension-unstable-v1.xml protocol/gtk-shell.xml protocol/relative-pointer-unstable-v1.xml protocol/text-input-unstable-v1.xml protocol/pointer-constraints-unstable-v1.xml
AUXFILES := Makefile README LICENSE AUTHORS sommelier@.service.in sommelier-x@.service.in sommelierrc sommelier.sh
ALLFILES := $(SRCFILES) $(XMLFILES) $(AUXFILES)
#GIT_VERSION := $(shell git describe --abbrev=4 --dirty --always --tags)
#DIST_VERSION := $(shell git describe --abbrev=0 --tags)
DIST_VERSION_BITS := $(subst ., ,$(DIST_VERSION))
DIST_VERSION_MAJOR := $(word 1,$(DIST_VERSION_BITS))
DIST_VERSION_MINOR := $(word 2,$(DIST_VERSION_BITS))
DIST_VERSION_MINOR_NEXT := $(shell expr $(DIST_VERSION_MINOR) + 1)
CFLAGS=-g -Wall `pkg-config --cflags libdrm xcb xcb-composite xcb-xfixes wayland-server wayland-client gbm pixman-1` -I. -D_GNU_SOURCE=1 -DWL_HIDE_DEPRECATED=1 -DXWAYLAND_PATH=\"$(PREFIX)/bin/Xwayland\"
LDFLAGS=-lpthread -lm `pkg-config --libs libdrm xcb xcb-composite xcb-xfixes wayland-server wayland-client gbm pixman-1 xkbcommon`
DEPS = xdg-shell-unstable-v6-client-protocol.h xdg-shell-unstable-v6-server-protocol.h aura-shell-client-protocol.h viewporter-client-protocol.h viewporter-server-protocol.h linux-dmabuf-unstable-v1-client-protocol.h drm-server-protocol.h keyboard-extension-unstable-v1-client-protocol.h gtk-shell-server-protocol.h relative-pointer-unstable-v1-server-protocol.h relative-pointer-unstable-v1-client-protocol.h text-input-unstable-v1-client-protocol.h text-input-unstable-v1-server-protocol.h pointer-constraints-unstable-v1-client-protocol.h pointer-constraints-unstable-v1-server-protocol.h
OBJECTS = sommelier.o sommelier-compositor.o sommelier-data-device-manager.o sommelier-display.o sommelier-drm.o sommelier-gtk-shell.o sommelier-output.o sommelier-relative-pointer-manager.o sommelier-seat.o sommelier-shell.o sommelier-shm.o sommelier-subcompositor.o sommelier-text-input.o sommelier-viewporter.o sommelier-xdg-shell.o sommelier-pointer-constraints.o xdg-shell-unstable-v6-protocol.o aura-shell-protocol.o viewporter-protocol.o linux-dmabuf-unstable-v1-protocol.o drm-protocol.o keyboard-extension-unstable-v1-protocol.o gtk-shell-protocol.o relative-pointer-unstable-v1-protocol.o text-input-unstable-v1-protocol.o pointer-constraints-unstable-v1-protocol.o
#all: sommelier sommelier@.service sommelier-x@.service
all: sommelier
%.service: %.service.in
$(SED) \
-e 's|@bindir[@]|$(BINDIR)|g' \
-e 's|@sysconfdir[@]|$(SYSCONFDIR)|g' \
-e 's|@version[@]|$(DIST_VERSION)|g' \
$< > $@
sommelier: $(OBJECTS)
$(CC) $(OBJECTS) -o sommelier $(LDFLAGS)
strip -s sommelier
%-protocol.c: protocol/%.xml
wayland-scanner private-code < $< > $@
%-client-protocol.h: protocol/%.xml
wayland-scanner client-header < $< > $@
%-server-protocol.h: protocol/%.xml
wayland-scanner server-header < $< > $@
%.o: %.c
$(CC) -c -o $@ $< $(CFLAGS)
$(OBJECTS): $(DEPS)
.PHONY: all install uninstall update-version dist deb version-clean clean style check-style tidy
install: all
install -D sommelier \
$(DESTDIR)$(PREFIX)/bin/sommelier
install -D sommelierrc $(DESTDIR)$(SYSCONFDIR)/sommelierrc
install -m 644 -D sommelier@.service \
$(DESTDIR)$(PREFIX)/lib/systemd/user/sommelier@.service
install -m 644 -D sommelier-x@.service \
$(DESTDIR)$(PREFIX)/lib/systemd/user/sommelier-x@.service
install -m 644 -D sommelier.sh $(DESTDIR)$(SYSCONFDIR)/profile.d/sommelier.sh
uninstall:
rm -f $(DESTDIR)$(PREFIX)/bin/sommelier
rm -f $(DESTDIR)$(SYSCONFDIR)/sommelierrc
rm -f $(DESTDIR)$(PREFIX)/lib/systemd/user/sommelier@.service
rm -f $(DESTDIR)$(PREFIX)/lib/systemd/user/sommelier-x@.service
rm -f $(DESTDIR)$(SYSCONFDIR)/profile.d/sommelier.sh
update-version:
dch -v $(DIST_VERSION_MAJOR).$(DIST_VERSION_MINOR_NEXT)-1
git commit -m 'debian/changelog: bump to version $(DIST_VERSION_MAJOR).$(DIST_VERSION_MINOR_NEXT)' debian/changelog
$(SED) -i -e 's/VERSION "[0-9.]*"/VERSION "$(DIST_VERSION_MAJOR).$(DIST_VERSION_MINOR_NEXT)"/g' version.h
git tag $(DIST_VERSION_MAJOR).$(DIST_VERSION_MINOR_NEXT)
dist: $(DEPS)
mkdir -p sommelier-$(DIST_VERSION)
cp -r $(ALLFILES) $(DEPS) debian sommelier-$(DIST_VERSION)
tar czf sommelier-$(DIST_VERSION).tar.gz sommelier-$(DIST_VERSION)
rm -rf sommelier-$(DIST_VERSION)
deb: dist
ln -sf sommelier-$(DIST_VERSION).tar.gz sommelier_$(DIST_VERSION).orig.tar.gz
tar xzf sommelier-$(DIST_VERSION).tar.gz
cd sommelier-$(DIST_VERSION) && debuild -i -us -uc -b
rm -rf sommelier-$(DIST_VERSION) sommelier_$(DIST_VERSION).orig.tar.gz
clean:
rm -f *~ *-protocol.c *-protocol.h *.o sommelier sommelier@.service \
sommelier-x@.service sommelier-*.tar.gz sommelier*.deb \
sommelier_*.build sommelier_*.buildinfo sommelier_*.changes
style: $(DEPS)
@for src in $(SRCFILES) ; do \
echo "Formatting $$src..."; \
$(CLANG_FORMAT) -i "$$src"; \
$(CLANG_TIDY) -checks='-*,readability-identifier-naming' \
-config="{CheckOptions: [ \
{ key: readability-identifier-naming.StructCase, value: lower_case }, \
{ key: readability-identifier-naming.FunctionCase, value: lower_case }, \
{ key: readability-identifier-naming.VariableCase, value: lower_case }, \
{ key: readability-identifier-naming.GlobalConstantCase, value: lower_case }, \
{ key: readability-identifier-naming.EnumConstantCase, value: UPPER_CASE } \
]}" "$$src"; \
done
@echo "Done"
check-style:
@for src in $(SRCFILES) ; do \
var=`$(CLANG_FORMAT) "$$src" | diff "$$src" - | wc -l`; \
if [ $$var -ne 0 ] ; then \
echo "$$src does not respect the coding style (diff: $$var lines)"; \
exit 1; \
fi; \
done
@echo "Style check passed"
tidy: $(DEPS)
@for src in $(SRCFILES); do \
echo "Running tidy on $$src..."; \
$(CLANG_TIDY) -checks="-*,modernize-use-auto,modernize-use-nullptr, \
readability-else-after-return,readability-simplify-boolean-expr, \
readability-redundant-member-init,modernize-use-default-member-init, \
modernize-use-equals-default,modernize-use-equals-delete, \
modernize-use-using,modernize-loop-convert, \
cppcoreguidelines-no-malloc,misc-redundant-expression" \
"$$src"; \
done
@echo "Done"

View File

@ -1,5 +1,4 @@
set noparent
reveman@chromium.org
cpelling@google.com
davidriley@chromium.org
hollingum@google.com
sidereal@google.com
davidriley@chromium.org

View File

@ -5,14 +5,14 @@ compositing to a 'host' compositor. Sommelier includes a set of features that
allows it to run inside a tight jail or virtual machine.
Sommelier can run as service or as a wrapper around the execution of a
program. As a service, it spawns new processes as needed to service clients.
The parent process is called the master sommelier.
program. As a service, called the parent sommelier, it spawns new processes as
needed to service clients.
## Sommeliers
### Master Sommelier
### Parent Sommelier
The master sommelier instance will create a wayland socket in XDG_RUNTIME_DIR
The parent sommelier instance will create a wayland socket in XDG_RUNTIME_DIR
and accept connections from regular wayland clients. Each connection will be
serviced by spawning a child sommelier process.
@ -57,12 +57,6 @@ host compositor. Sommelier provides a shared memory driver option as a
solution for this. What's the most appropriate option depends on the host
compositor and device drivers available for allocating buffers.
### Noop
The `noop` shared memory driver simply forwards buffers to the host without
any special processing. This requires that the client inside the container is
using memory that can be shared with the host compositor.
### VirtWL
The `virtwl` driver creates a set of intermediate virtwl buffers for each
@ -81,15 +75,6 @@ are:
* Host compositor can avoid expensive texture uploads.
* HW overlays can be used for presentation if support by the host compositor.
### DMABuf
The `dmabuf` driver is similar to the `virtwl-dmabuf` driver. It creates a set
of intermediate buffers for each surface and copies minimal damaged areas from
the clients standard shared memory buffer into the DMABuf buffer. However,
the buffer is allocated using a DRM device and a prime FD is used to access
buffer memory inside the container. Intermediate buffers are shared with the
host compositor using the linux_dmabuf protocol.
## Damage Tracking
Shared memory drivers that use intermediate buffers require some form of
@ -127,12 +112,6 @@ Socket pairs created inside a container cannot always be shared with the
host compositor. Sommelier provides a data driver option as a solution
for this.
### Noop
The `noop` driver simply forwards socket pair FDs to the host without any
special processing. This requires that the client inside the container is
using socket pairs that can be shared with the host compositor.
### VirtWL
The `virtwl` driver creates a special pipe that can be shared with the host
@ -178,10 +157,11 @@ scaling used by sommelier and the host compositor.
An exact value for DPI is calculated by sommelier. However, many Linux
programs expect DPI to be one out of a well known set of values. Sommelier
solves this by adjusting DPI using a set of buckets. For example, given the
default set of buckets (72, 96, 160, 240), Sommelier will use 96 as DPI when
the exact value is 112, or 160 when exact value is 188. The DPI buckets that
set of buckets (72, 96, 160, 240), Sommelier will use 96 as DPI when the
exact value is 112, or 160 when exact value is 188. The DPI buckets that
sommelier should use can be specified with `--dpi=[DPI[,DPI...]]`. Where,
`--dpi=””` will result in sommelier exposing the exact DPI value to clients.
`--dpi=””` will result in sommelier exposing the exact DPI value to clients
(this is the default behaviour).
### XCursor
@ -194,7 +174,7 @@ If the host compositor support dynamic handling of keyboard events, then
keyboard shortcuts are forwarded to the Linux program by default. A small set
of shortcuts are expected to be reserved by the host compositor. A list of
reserved shortcuts on Chrome OS can be found
[here](https://chromium.googlesource.com/chromium/src/+/master/ash/accelerators/accelerator_table.h#22).
[here](https://chromium.googlesource.com/chromium/src/+/HEAD/ash/accelerators/accelerator_table.h#22).
Theres unfortunately no reliable way to detect if a Linux program handled a
key event or not. This means that all non-reserved shortcuts that the user
@ -204,7 +184,7 @@ the "launcher" button during normal usage. The "launcher" button event is
forwarded to Linux programs by default so it wont work when a Linux program
has keyboard focus unless this shortcut is explicitly listed as an accelerator.
Sommelier provides the `--accelerator=ACCELERATORS` flag for this purpose.
Sommelier provides the `--accelerators=ACCELERATORS` flag for this purpose.
`ACCELERATORS` is a comma separated list of accelerators that shouldnt be
forwarded to the Linux program but instead handled by the host compositor.
Each accelerator can contain a list of modifiers (e.g. `<Control><Alt>`) and
@ -214,15 +194,20 @@ above (which happens to have XKB keysym `Super_L` on the Chromebook Pixel),
`--accelerators=Super_L` needs to be passed to sommelier for the this button to
bring up the application launcher when Linux programs have keyboard focus.
Consistent with other flags, `SOMMELIER_ACCELERATORS` environment variable can
There is also the `--windowed-accelerators=WINDOWED_ACCELERATORS` flag for
accelerators that should be handled by the host compositor when the focused
window is windowed but not while it is fullscreen.
Consistent with other flags, `SOMMELIER_ACCELERATORS` and
`SOMMELIER_WINDOWED_ACCELERATORS` environment variable can
be used as an alternative to the command line flag.
## Examples
Start master sommelier and use wayland-1 as name of socket to listen on:
Start parent sommelier and use wayland-1 as name of socket to listen on:
```
sommelier --master --socket=wayland-1
sommelier --parent --socket=wayland-1
```
Start sommelier that runs weston-terminal with density scale multiplier 1.5:

View File

@ -1,8 +0,0 @@
# Set build arguments here. See `gn buildargs`.
pkg_config = "/usr/bin/pkg-config"
libdir = "/usr/lib/x86_64-linux-gnu/"
platform_subdir="vm_tools/sommelier"
cxx="g++"
cc="gcc"
ar="/usr/bin/ar"
#use.amd64=true

View File

@ -0,0 +1,2 @@
ryanneph@google.com
zzyiwei@google.com

File diff suppressed because it is too large Load Diff

View File

@ -1,20 +1,22 @@
// Copyright 2018 The Chromium OS Authors. All rights reserved.
// Copyright 2018 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "sommelier.h"
#include "../sommelier.h" // NOLINT(build/include_directory)
#include "../sommelier-tracing.h" // NOLINT(build/include_directory)
#include <assert.h>
#include <gbm.h>
#include <libdrm/drm_fourcc.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <xf86drm.h>
#include "virtgpu_drm.h"
#include "../virtualization/linux-headers/virtgpu_drm.h" // NOLINT(build/include_directory)
#include "drm-server-protocol.h"
#include "linux-dmabuf-unstable-v1-client-protocol.h"
#include "drm-server-protocol.h" // NOLINT(build/include_directory)
#include "linux-dmabuf-unstable-v1-client-protocol.h" // NOLINT(build/include_directory)
struct sl_host_drm {
struct sl_context* ctx;
@ -27,6 +29,7 @@ struct sl_host_drm {
static void sl_drm_authenticate(struct wl_client* client,
struct wl_resource* resource,
uint32_t id) {
TRACE_EVENT("drm", "sl_drm_authenticate");
wl_drm_send_authenticated(resource);
}
@ -68,6 +71,7 @@ static void sl_drm_sync(struct sl_context* ctx,
// device given to sommelier.
memset(&prime_handle, 0, sizeof(prime_handle));
prime_handle.fd = sync_point->fd;
TRACE_EVENT("drm", "sl_drm_sync", "prime_fd", prime_handle.fd);
ret = drmIoctl(drm_fd, DRM_IOCTL_PRIME_FD_TO_HANDLE, &prime_handle);
if (!ret) {
struct drm_virtgpu_3d_wait wait_arg;
@ -100,7 +104,9 @@ static void sl_drm_create_prime_buffer(struct wl_client* client,
int32_t stride1,
int32_t offset2,
int32_t stride2) {
struct sl_host_drm* host = wl_resource_get_user_data(resource);
TRACE_EVENT("drm", "sl_drm_create_prime_buffer", "id", id);
struct sl_host_drm* host =
static_cast<sl_host_drm*>(wl_resource_get_user_data(resource));
struct zwp_linux_buffer_params_v1* buffer_params;
assert(name >= 0);
@ -125,16 +131,18 @@ static void sl_drm_create_prime_buffer(struct wl_client* client,
prime_handle.fd = name;
ret = drmIoctl(drm_fd, DRM_IOCTL_PRIME_FD_TO_HANDLE, &prime_handle);
if (!ret) {
struct drm_virtgpu_resource_info info_arg;
struct drm_virtgpu_resource_info_cros info_arg;
struct drm_gem_close gem_close;
// Then attempts to get resource information. This will fail silently if
// the drm device passed to sommelier is not a virtio-gpu device.
memset(&info_arg, 0, sizeof(info_arg));
info_arg.bo_handle = prime_handle.handle;
ret = drmIoctl(drm_fd, DRM_IOCTL_VIRTGPU_RESOURCE_INFO, &info_arg);
info_arg.type = VIRTGPU_RESOURCE_INFO_TYPE_EXTENDED;
ret = drmIoctl(drm_fd, DRM_IOCTL_VIRTGPU_RESOURCE_INFO_CROS, &info_arg);
// Correct stride0 if we are able to get proper resource info.
if (!ret) {
if (info_arg.stride)
stride0 = info_arg.stride;
is_gpu_buffer = 1;
}
@ -148,17 +156,40 @@ static void sl_drm_create_prime_buffer(struct wl_client* client,
buffer_params =
zwp_linux_dmabuf_v1_create_params(host->ctx->linux_dmabuf->internal);
zwp_linux_buffer_params_v1_add(buffer_params, name, 0, offset0, stride0, 0,
0);
zwp_linux_buffer_params_v1_add(buffer_params, name, 0, offset0, stride0,
DRM_FORMAT_MOD_INVALID >> 32,
DRM_FORMAT_MOD_INVALID & 0xffffffff);
struct sl_host_buffer* host_buffer =
sl_create_host_buffer(client, id,
sl_create_host_buffer(host->ctx, client, id,
zwp_linux_buffer_params_v1_create_immed(
buffer_params, width, height, format, 0),
width, height);
width, height, /*is_drm=*/true);
if (is_gpu_buffer) {
host_buffer->sync_point = sl_sync_point_create(name);
host_buffer->sync_point->sync = sl_drm_sync;
host_buffer->shm_format = sl_shm_format_for_drm_format(format);
// Create our DRM PRIME mmap container
// This is simply a container that records necessary information
// to map the DRM buffer through the GBM API's.
// The GBM API's may need to perform a rather heavy copy of the
// buffer into memory accessible by the CPU to perform the mapping
// operation.
// For this reason, the GBM mapping API's will not be used until we
// are absolutely certain that the buffers contents need to be
// accessed. This will be done through a call to sl_mmap_begin_access.
//
// We are also checking for a single plane format as this container
// is currently only defined for single plane format buffers.
if (sl_shm_num_planes_for_shm_format(host_buffer->shm_format) == 1) {
host_buffer->shm_mmap = sl_drm_prime_mmap_create(
host->ctx->gbm, name,
sl_shm_bpp_for_shm_format(host_buffer->shm_format),
sl_shm_num_planes_for_shm_format(host_buffer->shm_format), stride0,
width, height, format);
}
} else {
close(name);
}
@ -171,18 +202,20 @@ static const struct wl_drm_interface sl_drm_implementation = {
sl_drm_create_prime_buffer};
static void sl_destroy_host_drm(struct wl_resource* resource) {
struct sl_host_drm* host = wl_resource_get_user_data(resource);
struct sl_host_drm* host =
static_cast<sl_host_drm*>(wl_resource_get_user_data(resource));
zwp_linux_dmabuf_v1_destroy(host->linux_dmabuf_proxy);
wl_callback_destroy(host->callback);
wl_resource_set_user_data(resource, NULL);
free(host);
delete host;
}
static void sl_drm_format(void* data,
struct zwp_linux_dmabuf_v1* linux_dmabuf,
uint32_t format) {
struct sl_host_drm* host = zwp_linux_dmabuf_v1_get_user_data(linux_dmabuf);
struct sl_host_drm* host = static_cast<sl_host_drm*>(
zwp_linux_dmabuf_v1_get_user_data(linux_dmabuf));
switch (format) {
case WL_DRM_FORMAT_RGB565:
@ -191,6 +224,7 @@ static void sl_drm_format(void* data,
case WL_DRM_FORMAT_XRGB8888:
case WL_DRM_FORMAT_XBGR8888:
wl_drm_send_format(host->resource, format);
break;
default:
break;
}
@ -208,7 +242,8 @@ static const struct zwp_linux_dmabuf_v1_listener sl_linux_dmabuf_listener = {
static void sl_drm_callback_done(void* data,
struct wl_callback* callback,
uint32_t serial) {
struct sl_host_drm* host = wl_callback_get_user_data(callback);
struct sl_host_drm* host =
static_cast<sl_host_drm*>(wl_callback_get_user_data(callback));
if (host->ctx->drm_device)
wl_drm_send_device(host->resource, host->ctx->drm_device);
@ -224,10 +259,7 @@ static void sl_bind_host_drm(struct wl_client* client,
uint32_t version,
uint32_t id) {
struct sl_context* ctx = (struct sl_context*)data;
struct sl_host_drm* host;
host = malloc(sizeof(*host));
assert(host);
struct sl_host_drm* host = new sl_host_drm();
host->ctx = ctx;
host->version = MIN(version, 2);
host->resource =
@ -235,15 +267,13 @@ static void sl_bind_host_drm(struct wl_client* client,
wl_resource_set_implementation(host->resource, &sl_drm_implementation, host,
sl_destroy_host_drm);
host->linux_dmabuf_proxy = wl_registry_bind(
host->linux_dmabuf_proxy = static_cast<zwp_linux_dmabuf_v1*>(wl_registry_bind(
wl_display_get_registry(ctx->display), ctx->linux_dmabuf->id,
&zwp_linux_dmabuf_v1_interface, ctx->linux_dmabuf->version);
zwp_linux_dmabuf_v1_set_user_data(host->linux_dmabuf_proxy, host);
&zwp_linux_dmabuf_v1_interface, ctx->linux_dmabuf->version));
zwp_linux_dmabuf_v1_add_listener(host->linux_dmabuf_proxy,
&sl_linux_dmabuf_listener, host);
host->callback = wl_display_sync(ctx->display);
wl_callback_set_user_data(host->callback, host);
wl_callback_add_listener(host->callback, &sl_drm_callback_listener, host);
}

View File

@ -0,0 +1,173 @@
// Copyright 2021 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "sommelier-mmap.h" // NOLINT(build/include_directory)
#include <assert.h>
#include <unistd.h>
#include "../sommelier.h" // NOLINT(build/include_directory)
#include "../sommelier-tracing.h" // NOLINT(build/include_directory)
struct sl_mmap* sl_mmap_create(int fd,
size_t size,
size_t bpp,
size_t num_planes,
size_t offset0,
size_t stride0,
size_t offset1,
size_t stride1,
size_t y_ss0,
size_t y_ss1) {
TRACE_EVENT("shm", "sl_mmap_create");
struct sl_mmap* map = new sl_mmap();
map->refcount = 1;
map->fd = fd;
map->size = size;
map->map_type = SL_MMAP_SHM;
map->gbm_map_data = NULL;
map->gbmbo = NULL;
map->num_planes = num_planes;
map->bpp = bpp;
map->offset[0] = offset0;
map->stride[0] = stride0;
map->offset[1] = offset1;
map->stride[1] = stride1;
map->y_ss[0] = y_ss0;
map->y_ss[1] = y_ss1;
map->begin_write = NULL;
map->end_write = NULL;
map->buffer_resource = NULL;
map->addr =
mmap(NULL, size + offset0, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
errno_assert(map->addr != MAP_FAILED);
return map;
}
struct sl_mmap* sl_drm_prime_mmap_create(gbm_device* device,
int fd,
size_t bpp,
size_t num_planes,
size_t stride,
int32_t width,
int32_t height,
uint32_t drm_format) {
TRACE_EVENT("drm", "sl_drm_mmap_create");
struct sl_mmap* map = new sl_mmap();
assert(num_planes == 1);
map->refcount = 1;
map->fd = fd;
map->num_planes = num_planes;
map->bpp = bpp;
map->offset[0] = 0;
map->stride[0] = stride;
map->offset[1] = 0;
map->stride[1] = 0;
map->y_ss[0] = 1;
map->y_ss[1] = 1;
map->begin_write = NULL;
map->end_write = NULL;
map->buffer_resource = NULL;
map->map_type = SL_MMAP_DRM_PRIME;
map->gbm_map_data = NULL;
map->addr = NULL;
map->gbmbo = NULL;
// Prefill in the gbm_import_data structure
map->gbm_import_data.fd = fd;
map->gbm_import_data.width = width;
map->gbm_import_data.height = height;
map->gbm_import_data.stride = stride;
map->gbm_import_data.format = drm_format;
// Save the device as we will need it later
map->gbm_device_object = device;
return map;
}
struct sl_mmap* sl_mmap_ref(struct sl_mmap* map) {
TRACE_EVENT("shm", "sl_mmap_ref");
map->refcount++;
return map;
}
bool sl_mmap_begin_access(struct sl_mmap* map) {
uint32_t ret_stride;
// This function is to be used on the DRM PRIME mmap path.
// It is used to ensure we can actually access the resource
// when its contents are needed.
// If we have any other mmap type, we should simply return true
// under the assumption that they do not need to perform this extra
// check
if (map->map_type != SL_MMAP_DRM_PRIME)
return true;
// Attempt to import (and map) the GBM BO
// If we cannot do so, return false so the upper layers
// can respond appropriately.
map->gbmbo = gbm_bo_import(map->gbm_device_object, GBM_BO_IMPORT_FD,
reinterpret_cast<void*>(&map->gbm_import_data),
GBM_BO_USE_LINEAR);
if (!map->gbmbo) {
return false;
}
map->gbm_map_data = NULL;
map->addr = gbm_bo_map(map->gbmbo, 0, 0, map->gbm_import_data.width,
map->gbm_import_data.height, GBM_BO_TRANSFER_READ,
&ret_stride, &map->gbm_map_data);
if (!map->addr) {
gbm_bo_destroy(map->gbmbo);
return false;
}
map->stride[0] = ret_stride;
map->size = ret_stride * map->gbm_import_data.height;
return true;
}
void sl_mmap_end_access(struct sl_mmap* map) {
if (map->map_type != SL_MMAP_DRM_PRIME)
return;
if (map->addr && map->gbm_map_data) {
gbm_bo_unmap(map->gbmbo, map->gbm_map_data);
map->addr = NULL;
map->gbm_map_data = NULL;
}
if (map->gbmbo) {
gbm_bo_destroy(map->gbmbo);
map->gbmbo = NULL;
}
}
void sl_mmap_unref(struct sl_mmap* map) {
TRACE_EVENT("shm", "sl_mmap_unref");
if (map->refcount-- == 1) {
switch (map->map_type) {
case SL_MMAP_SHM:
munmap(map->addr, map->size + map->offset[0]);
if (map->fd != -1)
close(map->fd);
delete map;
break;
case SL_MMAP_DRM_PRIME:
// Invoke end_access just in case
sl_mmap_end_access(map);
if (map->fd != -1)
close(map->fd);
delete map;
break;
default:
break;
}
}
}

View File

@ -0,0 +1,65 @@
// Copyright 2021 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef VM_TOOLS_SOMMELIER_COMPOSITOR_SOMMELIER_MMAP_H_
#define VM_TOOLS_SOMMELIER_COMPOSITOR_SOMMELIER_MMAP_H_
#include <gbm.h>
#include <sys/types.h>
typedef void (*sl_begin_end_access_func_t)(int fd, struct sl_context* ctx);
enum slMmapType {
SL_MMAP_NONE, // None
SL_MMAP_SHM, // SHM mmap type
SL_MMAP_DRM_PRIME // DRM PRIME mmap type
};
struct sl_mmap {
int refcount;
int fd;
void* addr;
struct gbm_bo* gbmbo;
void* gbm_map_data;
gbm_device* gbm_device_object;
size_t size;
size_t bpp;
size_t num_planes;
size_t offset[2];
size_t stride[2];
size_t y_ss[2];
sl_begin_end_access_func_t begin_write;
sl_begin_end_access_func_t end_write;
slMmapType map_type;
struct gbm_import_fd_data gbm_import_data;
struct wl_resource* buffer_resource;
};
struct sl_mmap* sl_drm_prime_mmap_create(gbm_device* device,
int fd,
size_t bpp,
size_t num_planes,
size_t stride,
int32_t width,
int32_t height,
uint32_t drm_format);
struct sl_mmap* sl_mmap_create(int fd,
size_t size,
size_t bpp,
size_t num_planes,
size_t offset0,
size_t stride0,
size_t offset1,
size_t stride1,
size_t y_ss0,
size_t y_ss1);
bool sl_mmap_begin_access(struct sl_mmap* map);
void sl_mmap_end_access(struct sl_mmap* map);
struct sl_mmap* sl_mmap_ref(struct sl_mmap* map);
void sl_mmap_unref(struct sl_mmap* map);
#endif // VM_TOOLS_SOMMELIER_COMPOSITOR_SOMMELIER_MMAP_H_

View File

@ -1,16 +1,17 @@
// Copyright 2018 The Chromium OS Authors. All rights reserved.
// Copyright 2018 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "sommelier.h"
#include "../sommelier.h" // NOLINT(build/include_directory)
#include "../sommelier-tracing.h" // NOLINT(build/include_directory)
#include <assert.h>
#include <stdlib.h>
#include <unistd.h>
#include <wayland-client.h>
#include "drm-server-protocol.h"
#include "linux-dmabuf-unstable-v1-client-protocol.h"
#include "drm-server-protocol.h" // NOLINT(build/include_directory)
#include "linux-dmabuf-unstable-v1-client-protocol.h" // NOLINT(build/include_directory)
struct sl_host_shm_pool {
struct sl_shm* shm;
@ -129,28 +130,38 @@ static void sl_host_shm_pool_create_host_buffer(struct wl_client* client,
int32_t height,
int32_t stride,
uint32_t format) {
struct sl_host_shm_pool* host = wl_resource_get_user_data(resource);
struct sl_host_shm_pool* host =
static_cast<sl_host_shm_pool*>(wl_resource_get_user_data(resource));
if (host->shm->ctx->shm_driver == SHM_DRIVER_NOOP) {
if (host->shm->ctx->channel == NULL) {
// Running in noop mode, without virtualization.
assert(host->proxy);
sl_create_host_buffer(client, id,
sl_create_host_buffer(host->shm->ctx, client, id,
wl_shm_pool_create_buffer(host->proxy, offset, width,
height, stride, format),
width, height);
} else {
struct sl_host_buffer* host_buffer =
sl_create_host_buffer(client, id, NULL, width, height);
width, height, /*is_drm=*/true);
return;
}
struct sl_host_buffer* host_buffer = sl_create_host_buffer(
host->shm->ctx, client, id, NULL, width, height, /*is_drm=*/false);
host_buffer->shm_format = format;
host_buffer->shm_mmap = sl_mmap_create(
dup(host->fd), sl_size_for_shm_format(format, height, stride),
host->fd, sl_size_for_shm_format(format, height, stride),
sl_shm_bpp_for_shm_format(format),
sl_shm_num_planes_for_shm_format(format), offset, stride,
offset + sl_offset_for_shm_format_plane(format, height, stride, 1),
stride, sl_y_subsampling_for_shm_format_plane(format, 0),
sl_y_subsampling_for_shm_format_plane(format, 1));
// In the case of mmaps created from the client buffer, we want to be able
// to close the FD when the client releases the shm pool (i.e. when it's
// done transferring) as opposed to when the pool is freed (i.e. when we're
// done drawing).
// We do this by removing the handle to the FD after it has been mmapped,
// which prevents a double-close.
host_buffer->shm_mmap->fd = -1;
host_buffer->shm_mmap->buffer_resource = host_buffer->resource;
}
}
static void sl_host_shm_pool_destroy(struct wl_client* client,
@ -161,7 +172,8 @@ static void sl_host_shm_pool_destroy(struct wl_client* client,
static void sl_host_shm_pool_resize(struct wl_client* client,
struct wl_resource* resource,
int32_t size) {
struct sl_host_shm_pool* host = wl_resource_get_user_data(resource);
struct sl_host_shm_pool* host =
static_cast<sl_host_shm_pool*>(wl_resource_get_user_data(resource));
if (host->proxy)
wl_shm_pool_resize(host->proxy, size);
@ -172,14 +184,15 @@ static const struct wl_shm_pool_interface sl_shm_pool_implementation = {
sl_host_shm_pool_resize};
static void sl_destroy_host_shm_pool(struct wl_resource* resource) {
struct sl_host_shm_pool* host = wl_resource_get_user_data(resource);
struct sl_host_shm_pool* host =
static_cast<sl_host_shm_pool*>(wl_resource_get_user_data(resource));
if (host->fd >= 0)
close(host->fd);
if (host->proxy)
wl_shm_pool_destroy(host->proxy);
wl_resource_set_user_data(resource, NULL);
free(host);
delete host;
}
static void sl_shm_create_host_pool(struct wl_client* client,
@ -187,11 +200,9 @@ static void sl_shm_create_host_pool(struct wl_client* client,
uint32_t id,
int fd,
int32_t size) {
struct sl_host_shm* host = wl_resource_get_user_data(resource);
struct sl_host_shm_pool* host_shm_pool;
host_shm_pool = malloc(sizeof(*host_shm_pool));
assert(host_shm_pool);
struct sl_host_shm* host =
static_cast<sl_host_shm*>(wl_resource_get_user_data(resource));
struct sl_host_shm_pool* host_shm_pool = new sl_host_shm_pool();
host_shm_pool->shm = host->shm;
host_shm_pool->fd = -1;
@ -202,17 +213,13 @@ static void sl_shm_create_host_pool(struct wl_client* client,
&sl_shm_pool_implementation, host_shm_pool,
sl_destroy_host_shm_pool);
switch (host->shm->ctx->shm_driver) {
case SHM_DRIVER_NOOP:
if (host->shm->ctx->channel == NULL) {
// Running in noop mode, without virtualization.
host_shm_pool->proxy = wl_shm_create_pool(host->shm_proxy, fd, size);
wl_shm_pool_set_user_data(host_shm_pool->proxy, host_shm_pool);
close(fd);
break;
case SHM_DRIVER_DMABUF:
case SHM_DRIVER_VIRTWL:
case SHM_DRIVER_VIRTWL_DMABUF:
} else {
host_shm_pool->fd = fd;
break;
}
}
@ -220,7 +227,9 @@ static const struct wl_shm_interface sl_shm_implementation = {
sl_shm_create_host_pool};
static void sl_shm_format(void* data, struct wl_shm* shm, uint32_t format) {
struct sl_host_shm* host = wl_shm_get_user_data(shm);
TRACE_EVENT("shm", "sl_shm_format");
struct sl_host_shm* host =
static_cast<sl_host_shm*>(wl_shm_get_user_data(shm));
switch (format) {
case WL_SHM_FORMAT_RGB565:
@ -229,6 +238,7 @@ static void sl_shm_format(void* data, struct wl_shm* shm, uint32_t format) {
case WL_SHM_FORMAT_XRGB8888:
case WL_SHM_FORMAT_XBGR8888:
wl_shm_send_format(host->resource, format);
break;
default:
break;
}
@ -239,7 +249,8 @@ static const struct wl_shm_listener sl_shm_listener = {sl_shm_format};
static void sl_drm_format(void* data,
struct zwp_linux_dmabuf_v1* linux_dmabuf,
uint32_t format) {
struct sl_host_shm* host = zwp_linux_dmabuf_v1_get_user_data(linux_dmabuf);
struct sl_host_shm* host = static_cast<sl_host_shm*>(
zwp_linux_dmabuf_v1_get_user_data(linux_dmabuf));
// Forward SHM versions of supported formats.
switch (format) {
@ -274,14 +285,15 @@ static const struct zwp_linux_dmabuf_v1_listener sl_linux_dmabuf_listener = {
sl_drm_format, sl_drm_modifier};
static void sl_destroy_host_shm(struct wl_resource* resource) {
struct sl_host_shm* host = wl_resource_get_user_data(resource);
struct sl_host_shm* host =
static_cast<sl_host_shm*>(wl_resource_get_user_data(resource));
if (host->shm_proxy)
wl_shm_destroy(host->shm_proxy);
if (host->linux_dmabuf_proxy)
zwp_linux_dmabuf_v1_destroy(host->linux_dmabuf_proxy);
wl_resource_set_user_data(resource, NULL);
free(host);
delete host;
}
static void sl_bind_host_shm(struct wl_client* client,
@ -289,10 +301,7 @@ static void sl_bind_host_shm(struct wl_client* client,
uint32_t version,
uint32_t id) {
struct sl_context* ctx = (struct sl_context*)data;
struct sl_host_shm* host;
host = malloc(sizeof(*host));
assert(host);
struct sl_host_shm* host = new sl_host_shm();
host->shm = ctx->shm;
host->shm_proxy = NULL;
host->linux_dmabuf_proxy = NULL;
@ -300,26 +309,19 @@ static void sl_bind_host_shm(struct wl_client* client,
wl_resource_set_implementation(host->resource, &sl_shm_implementation, host,
sl_destroy_host_shm);
switch (ctx->shm_driver) {
case SHM_DRIVER_NOOP:
case SHM_DRIVER_VIRTWL:
host->shm_proxy = wl_registry_bind(
wl_display_get_registry(ctx->display), ctx->shm->id,
&wl_shm_interface, wl_resource_get_version(host->resource));
wl_shm_set_user_data(host->shm_proxy, host);
wl_shm_add_listener(host->shm_proxy, &sl_shm_listener, host);
break;
case SHM_DRIVER_VIRTWL_DMABUF:
case SHM_DRIVER_DMABUF:
if (ctx->channel != NULL && ctx->channel->supports_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);
host->linux_dmabuf_proxy = static_cast<zwp_linux_dmabuf_v1*>(
wl_registry_bind(wl_display_get_registry(ctx->display),
ctx->linux_dmabuf->id, &zwp_linux_dmabuf_v1_interface,
wl_resource_get_version(host->resource)));
zwp_linux_dmabuf_v1_add_listener(host->linux_dmabuf_proxy,
&sl_linux_dmabuf_listener, host);
break;
} else {
host->shm_proxy = static_cast<wl_shm*>(wl_registry_bind(
wl_display_get_registry(ctx->display), ctx->shm->id, &wl_shm_interface,
wl_resource_get_version(host->resource)));
wl_shm_add_listener(host->shm_proxy, &sl_shm_listener, host);
}
}

View File

@ -1,16 +0,0 @@
#ifndef _CONFIG_H_
#define _CONFIG_H_
/*
#ifndef XWAYLAND_PATH
#define XWAYLAND_PATH "/usr/bin/Xwayland"
#endif
*/
#define XWAYLAND_GL_DRIVER_PATH "/lib/x86_64-linux-gnu/dri"
#define XWAYLAND_SHM_DRIVER "virtwl"
#define SHM_DRIVER "virtwl"
#define VIRTWL_DEVICE "/dev/wl0"
#define PEER_CMD_PREFIX ""
#define FRAME_COLOR "#f2f2f2"
#define DARK_FRAME_COLOR "#323639"
#endif

View File

@ -1,319 +0,0 @@
// Copyright 2018 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wayland-client.h>
#include <wayland-client-protocol.h>
#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<struct demo_data*>(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<struct demo_data*>(data);
if (!strcmp("wl_compositor", interface)) {
data_ptr->compositor = reinterpret_cast<struct wl_compositor*>(
wl_registry_bind(registry, id, &wl_compositor_interface, version));
} else if (!strcmp("wl_shell", interface)) {
data_ptr->shell = reinterpret_cast<struct wl_shell*>(
wl_registry_bind(registry, id, &wl_shell_interface, version));
} else if (!strcmp("wl_shm", interface)) {
data_ptr->shm = reinterpret_cast<struct wl_shm*>(
wl_registry_bind(registry, id, &wl_shm_interface, version));
} else if (!strcmp("wl_output", interface)) {
data_ptr->output = reinterpret_cast<struct wl_output*>(
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<struct wl_seat*>(
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<struct demo_data*>(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<uint32_t*>(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<struct demo_data*>(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<struct demo_data*>(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, &registry_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;
}

View File

@ -1,90 +0,0 @@
// Copyright 2018 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#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;
}

View File

@ -1,135 +0,0 @@
#ifndef _LINUX_VIRTIO_WL_H
#define _LINUX_VIRTIO_WL_H
/*
* This header is BSD licensed so anyone can use the definitions to implement
* compatible drivers/servers.
*/
#include <linux/virtio_ids.h>
#include <linux/virtio_config.h>
#include <linux/virtwl.h>
#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 */

192
sommelier/meson.build Normal file
View File

@ -0,0 +1,192 @@
# Copyright 2020 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
project('sommelier', 'c', 'cpp',
default_options : ['cpp_std=c++17'])
#===============#
# Configuration #
#===============#
includes = []
cpp_args = []
#===============#
# Wayland Stuff #
#===============#
wl_scanner = find_program('wayland-scanner')
wl_generators = [
generator(
wl_scanner,
output: '@BASENAME@-code.c',
arguments: ['private-code', '@INPUT@', '@OUTPUT@']
),
generator(
wl_scanner,
output: '@BASENAME@-client-protocol.h',
arguments: ['client-header', '@INPUT@', '@OUTPUT@']
),
generator(
wl_scanner,
output: '@BASENAME@-server-protocol.h',
arguments: ['server-header', '@INPUT@', '@OUTPUT@']
),
]
wl_protocols = [
'protocol/aura-shell.xml',
'protocol/drm.xml',
'protocol/gaming-input-unstable-v2.xml',
'protocol/gtk-shell.xml',
'protocol/keyboard-extension-unstable-v1.xml',
'protocol/linux-dmabuf-unstable-v1.xml',
'protocol/linux-explicit-synchronization-unstable-v1.xml',
'protocol/pointer-constraints-unstable-v1.xml',
'protocol/relative-pointer-unstable-v1.xml',
'protocol/text-input-unstable-v1.xml',
'protocol/text-input-extension-unstable-v1.xml',
'protocol/text-input-x11-unstable-v1.xml',
'protocol/viewporter.xml',
'protocol/xdg-output-unstable-v1.xml',
'protocol/xdg-shell.xml',
]
wl_outs = []
foreach p : wl_protocols
foreach g : wl_generators
wl_outs += g.process(p)
endforeach
endforeach
#==========#
# Perfetto #
#==========#
tracing_sources = []
tracing_dependencies = []
if get_option('tracing')
tracing_dependencies = [
dependency('threads'),
dependency('perfetto'),
]
cpp_args += '-DPERFETTO_TRACING'
endif
#=================#
# Gamepad support #
#=================#
gamepad_sources = []
gamepad_dependencies = []
if get_option('gamepad')
gamepad_sources = [
'sommelier-gaming.cc',
]
gamepad_dependencies = [
dependency('libevdev'),
]
cpp_args += '-DGAMEPAD_SUPPORT'
endif
#===========#
# Sommelier #
#===========#
if get_option('commit_loop_fix')
cpp_args += '-DCOMMIT_LOOP_FIX'
endif
if get_option('black_screen_fix')
cpp_args += '-DBLACK_SCREEN_FIX'
endif
sommelier_defines = [
'-D_GNU_SOURCE',
'-DWL_HIDE_DEPRECATED',
'-DXWAYLAND_PATH="' + get_option('xwayland_path') + '"',
'-DXWAYLAND_GL_DRIVER_PATH="' + get_option('xwayland_gl_driver_path') + '"',
'-DFRAME_COLOR="' + get_option('frame_color') + '"',
'-DDARK_FRAME_COLOR="' + get_option('dark_frame_color') + '"',
]
libsommelier = static_library('sommelier',
sources: [
'compositor/sommelier-compositor.cc',
'compositor/sommelier-drm.cc',
'compositor/sommelier-mmap.cc',
'compositor/sommelier-shm.cc',
'sommelier-ctx.cc',
'sommelier-data-device-manager.cc',
'sommelier-display.cc',
'sommelier-gtk-shell.cc',
'sommelier-global.cc',
'sommelier-output.cc',
'sommelier-pointer-constraints.cc',
'sommelier-relative-pointer-manager.cc',
'sommelier-seat.cc',
'sommelier-shell.cc',
'sommelier-subcompositor.cc',
'sommelier-text-input.cc',
'sommelier-timing.cc',
'sommelier-tracing.cc',
'sommelier-transform.cc',
'sommelier-util.cc',
'sommelier-viewporter.cc',
'sommelier-xdg-shell.cc',
'sommelier-xshape.cc',
'sommelier.cc',
'sommelier-window.cc',
'virtualization/virtwl_channel.cc',
'virtualization/virtgpu_channel.cc',
] + wl_outs + tracing_sources + gamepad_sources,
dependencies: [
meson.get_compiler('cpp').find_library('m'),
dependency('gbm'),
dependency('libdrm'),
dependency('pixman-1'),
dependency('wayland-client'),
dependency('wayland-server'),
dependency('xcb'),
dependency('xcb-composite'),
dependency('xcb-shape'),
dependency('xcb-xfixes'),
dependency('xkbcommon'),
] + tracing_dependencies + gamepad_dependencies,
cpp_args: cpp_args + sommelier_defines,
include_directories: includes,
)
executable('sommelier',
install: true,
sources: [
'sommelier-main.cc',
] + wl_outs,
link_with: libsommelier,
cpp_args: cpp_args + sommelier_defines,
include_directories: includes,
)
if get_option('with_tests')
sommelier_test = executable('sommelier_test',
install: true,
sources: [
'sommelier_test.cc',
] + wl_outs,
link_with: libsommelier,
dependencies: [
dependency('gtest'),
dependency('gmock'),
dependency('pixman-1')
],
cpp_args: cpp_args + sommelier_defines,
include_directories: includes,
)
test('sommelier_test', sommelier_test)
endif

View File

@ -0,0 +1,59 @@
# Copyright 2020 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
option('xwayland_path',
type: 'string',
value: '/opt/google/cros-containers/bin/Xwayland',
description: 'path to Xwayland'
)
option('xwayland_gl_driver_path',
type: 'string',
value: '/opt/google/cros-containers/lib',
description: 'the GL driver path to use for Xwayland'
)
option('frame_color',
type: 'string',
value: '#f2f2f2',
description: 'frame color to use for Xwayland clients'
)
option('dark_frame_color',
type: 'string',
value: '#323639',
description: 'dark frame color to use for Xwayland clients'
)
option('tracing',
type: 'boolean',
value: false,
description: 'enable tracing via perfetto'
)
option('gamepad',
type: 'boolean',
value: false,
description: 'enable gamepad support'
)
# TODO(b/181077580): remove this in favour of a proper fix to the busy
# loop issue.
option('commit_loop_fix',
type: 'boolean',
value: false,
description: 'enable a fix to the commit-cycle, which is known to break some apps'
)
option('black_screen_fix',
type: 'boolean',
value: false,
description: 'enable a fix to fix some apps blackscreening when losing focus'
)
option('with_tests',
type: 'boolean',
value: true,
description: 'build the sommelier_test target'
)

View File

@ -2,7 +2,7 @@
<protocol name="aura_shell">
<copyright>
Copyright 2017 The Chromium Authors.
Copyright 2017 The Chromium Authors
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
@ -24,7 +24,7 @@
DEALINGS IN THE SOFTWARE.
</copyright>
<interface name="zaura_shell" version="6">
<interface name="zaura_shell" version="38">
<description summary="aura_shell">
The global interface exposing aura shell capabilities is used to
instantiate an interface extension for a wl_surface object.
@ -66,9 +66,101 @@
<arg name="output" type="object" interface="wl_output"
summary="the output"/>
</request>
<!-- Version 11 additions -->
<enum name="layout_mode">
<description summary="the layout mode">
Specifies the server's window layout mode.
</description>
<entry name="windowed" value="1" summary="multiple windows"/>
<entry name="tablet" value="2" summary="restricted mode for tablet"/>
</enum>
<event name="layout_mode" since="11">
<description summary="sends the layout_mode">
Sends the layout_mode used by the server.
</description>
<arg name="layout_mode" type="uint" summary="layout_mode enum"/>
</event>
<!-- Version 14 additions -->
<event name="bug_fix" since="14">
<description summary="sends a bug fix ID">
Sends a monorail ID of a bug fixed on the exo server that clients can
use to gate functionality.
</description>
<arg name="id" type="uint" summary="ID of a single bug fix"/>
</event>
<event name="desks_changed" since="22">
<description summary="sends names of desks">
Notifies when there is a change in global desks state. This is emitted on
desk name changes, desk addition/removal or desks are reordered.
"desk_names" argument contains the set of null terminated strings as names of desks.
</description>
<arg name="desk_names" type="array" summary="an array of existing desks' names"/>
</event>
<event name="desk_activation_changed" since="22">
<description summary="sends the index of the active desk">
Notifies when there is a change of the active desk.
</description>
<arg name="active_desk_index" type="int" summary="index of the active desk"/>
</event>
<!-- Version 24 additions -->
<event name="activated" since="24">
<description summary="activated surface changed">
Notifies client that the activated surface changed.
</description>
<arg name="gained_active" type="object" interface="wl_surface" allow-null="true"/>
<arg name="lost_active" type="object" interface="wl_surface" allow-null="true"/>
</event>
<!-- Version 26 additions -->
<request name="surface_submission_in_pixel_coordinates" since="26">
<description summary="surfaces will be submitted in pixel coordinates">
[Deprecated] Informs the server that when submitting surfaces, this
client will not use wl_surface_set_buffer_scale to report the scales,
nor will it apply scale via vp_viewporter. Instead the server should
apply an appropriate scale transform to have the submitted buffers
composited correctly.
</description>
</request>
<request name="get_aura_toplevel_for_xdg_toplevel" since="27">
<description summary="get aura toplevel">
Retrieve the aura toplevel interface for a given xdg toplevel interface
</description>
<arg name="id" type="new_id" interface="zaura_toplevel"
summary="the new aura toplevel interface id"/>
<arg name="toplevel" type="object" interface="xdg_toplevel"/>
</request>
<request name="get_aura_popup_for_xdg_popup" since="28">
<description summary="get aura popup">
Retrieve the aura popup interface for the given xdg popup interface
</description>
<arg name="id" type="new_id" interface="zaura_popup"
summary="the aura popup interface id"/>
<arg name="popup" type="object" interface="xdg_popup"/>
</request>
<request name="release" type="destructor" since="38">
<description summary="release zaura_shell object">
Using this request a client can tell the server that it is not going to
use the zaura_shell object anymore. This does not affect any other objects.
This is named "release" because "destroy" is a special named function used for
freeing wl_proxy objects. Naming it "destroy" will cause marshalling errors
when running on lower versioned hosts. All "release" requests here should be
renamed to "destroy" if we move to aura-shell v2.
</description>
</request>
</interface>
<interface name="zaura_surface" version="5">
<interface name="zaura_surface" version="38">
<description summary="aura shell interface to a wl_surface">
An additional interface to a wl_surface object, which allows the
client to access aura shell specific functionality for surface.
@ -85,7 +177,9 @@
<request name="set_frame">
<description summary="request a frame for surface">
Suggests a surface should use a specific frame.
[Deprecated] Suggests a surface should use a specific frame. Deprecated
since M105. See the set_decoration method on zaura_toplevel and
zaura_popup.
</description>
<arg name="type" type="uint" summary="the new frame type"/>
</request>
@ -129,9 +223,320 @@
</description>
<arg name="application_id" type="string" allow-null="true"/>
</request>
<!-- Version 7 additions -->
<request name="set_client_surface_id" since="7">
<description summary="set the client surface ID of this surface">
Deprecated. Please use set_client_surface_str_id instead.
Set the identifier of the surface assigned by the client.
</description>
<arg name="client_surface_id" type="int" />
</request>
<!-- Version 8 additions -->
<enum name="occlusion_change_reason">
<description summary="occlusion change reason">
Enum describing why an occlusion change happened. An occlusion change as a
result of a user action could include things like the user moving a window,
changing occlusion, or opening/closing a window, changing the occlusion.
</description>
<entry name="user_action" value="1" summary="occlusion changed as a result of a user action"/>
</enum>
<request name="set_occlusion_tracking" since="8">
<description summary="set tracked occlusion region">
Sets occlusion tracking on this surface. The client will be updated with a
new occlusion fraction when the amount of occlusion of this surface changes.
</description>
</request>
<request name="unset_occlusion_tracking" since="8">
<description summary="unset tracked occlusion region">
Unsets occlusion tracking for this surface.
</description>
</request>
<event name="occlusion_changed" since="8">
<description summary="Notifies on an occlusion change">
Notifies when there is a change in the amount this surface is occluded.
The occlusion update is sent as a fixed point number from 0 to 1, representing
the proportion of occlusion.
</description>
<arg name="occlusion_fraction" type="fixed"/>
<arg name="occlusion_reason" type="uint"/>
</event>
<!-- Version 9 additions -->
<request name="activate" since="9">
<description summary="Indicate that this window wants to be the active window">
Make this the active window. This usually implies something like
restacking this surface to the foreground. The compositor is free to
ignore this request if it deems the client to be misbehaving. Typically
this request will only be honoured in response to some user driven
event, such as executing an application or opening a file in a window
that already exists.
</description>
</request>
<request name="draw_attention" since="9">
<description summary="Indicate that this window wants some of the user's attention">
Draw attention to this surface in a way that does not change the user's
focus. This usually means animating window decorations or taskbar icons.
The compositor can still ignore this request if it deems fit, but unlike
draw_focus, these requests are expected to come from background tasks,
and are more likely to be honoured.
</description>
</request>
<!-- Version 10 additions -->
<enum name="fullscreen_mode">
<description
summary="Specifies the behavior of the surface in fullscreen.">
Possible windowing system behaviors if this surface were to go
fullscreen.
</description>
<entry
name="immersive"
value="0"
summary="user can access system UIs such as the shelf and window frame
by pointing to, or swiping over, the screen edge"/>
<entry
name="plain"
value="1"
summary="user cannot access system UIs using mouse/touches"/>
</enum>
<request name="set_fullscreen_mode" since="10">
<description summary="Sets the behavior of the surface in fullscreen.">
Suggests how the windowing system should behave if this surface were
to go fullscreen. Does not make the surface fullscreen.
Typically the default mode is "immersive".
</description>
<arg name="mode" type="uint" enum="fullscreen_mode"/>
</request>
<!-- Version 12 additions -->
<request name="set_client_surface_str_id" since="12">
<description summary="set the client surface ID of this surface">
Set the identifier of the surface assigned by the client.
</description>
<arg name="client_surface_id" type="string" />
</request>
<!-- Version 15 additions -->
<request name="set_server_start_resize" since="15">
<description summary="request a server-side shadow for surface">
Suggests a surface to have client-side decoration, but
server-side decides when and where to start the resize. The server may also
apply visual effects to indicate that the resize operation is ongoing.
</description>
</request>
<!-- Version 16 additions -->
<enum name="snap_direction">
<description summary="surface snap directions">
Surface snap directions.
</description>
<entry name="none" value="0" summary=""/>
<entry name="left" value="1" summary=""/>
<entry name="right" value="2" summary=""/>
</enum>
<request name="intent_to_snap" since="16">
<description summary="client intents to snap the surface.">
Notify (or inform) the server the client's intent to snap the window.
To inform it's no longer willing to snap, send 'none'.
</description>
<arg name="direction" type="uint" enum="snap_direction"/>
</request>
<request name="set_snap_left" since="16">
<description summary="snap the surface to the left.">
Request that surface is snapped to the left.
</description>
</request>
<request name="set_snap_right" since="16">
<description summary="snap the surface to the right.">
Request that surface is snapped to the right.
</description>
</request>
<request name="unset_snap" since="16">
<description summary="Unset the surface snap.">
Request that surface resets snapping.
</description>
</request>
<!-- Version 17 additions -->
<event name="lock_frame_normal" since="17">
<description summary="Notify the client that server intent to lock window in normal or restore state">
Notifies the client to lock window in normal or restore state. When
window is locked, the window frame should look like it is in restored
state, but actually isn't. Locking happends while dragging a maximized
window.
</description>
</event>
<event name="unlock_frame_normal" since="17">
<description summary="Notify the client that server intent to unlock window's normal or restore state">
Notifies the client to unlock window if it is previously locked.
Unlocking happends while dragging a maximized window.
</description>
</event>
<!-- Version 18 additions -->
<request name="set_window_session_id" since="18">
<description summary="set surface window session id">
Set window session id to the surface.
</description>
<arg name="id" type="int" summary="window session id"/>
</request>
<!-- Version 19 additions -->
<request name="set_can_go_back" since="19">
<description summary="Set the minimize-on-back-gesture behavior.">
Sets that the surface can go back as per its navigation list.
This allows the server to react to by minimizing the window upon a
system wide back gesture.
</description>
</request>
<request name="unset_can_go_back" since="19">
<description summary="Unset the minimize-on-back-gesture behavior.">
Unsets that the surface can go back as per its navigation list.
See above.
</description>
</request>
<!-- Version 20 additions -->
<request name="set_pip" since="20">
<description summary="Set pip for the surface.">
Requests that the surface is set to Picture-in-Picture (PIP).
</description>
</request>
<request name="unset_pip" since="20">
<description summary="Unset pip for the surface.">
Requests that the surface is unset from Picture-in-Picture (PIP).
</description>
</request>
<request name="set_aspect_ratio" since="20">
<description summary="Set aspect ratio for the surface.">
Sets the aspect ratio of the surface.
</description>
<arg name="width" type="int"/>
<arg name="height" type="int"/>
</request>
<!-- Version 21 additions -->
<enum name="occlusion_state">
<description summary="surface occlusion state">
Describes the occlusion state of a surface.
</description>
<entry name="unknown" value="0" summary="The surface's occlusion state isn't tracked"/>
<entry name="visible" value="1" summary="The surface is visible"/>
<entry name="occluded" value="2" summary="The surface is occluded"/>
<entry name="hidden" value="3" summary="The surface is not visible"/>
</enum>
<event name="occlusion_state_changed" since="21">
<description summary="Notify the client that the occlusion state changed">
Notifies the client that the occlusion state of a window has changed. Clients
will only receive these messages if they previously request occlusion tracking
via set_occlusion_tracking for a particular surface.
</description>
<arg name="mode" type="uint" enum="occlusion_state"/>
</event>
<!-- Version 22 additions -->
<request name="move_to_desk" since="22">
<description summary="move to desk">
If |index| equals -1, requests that the server toggles whether client
is visible on all workspaces.
If |index| is not -1, requests that the server moves the client to the desk at
|index|.
</description>
<arg name="index" type="int"/>
</request>
<event name="desk_changed" since="22">
<description summary="window desk state changed">
Notifies when there is a change in the desk state of a window.
This is emitted when a window is moved to another desk or
when its assigned-to-all-desks state changes.
</description>
<arg name="state" type="int" summary="index of desk or -1 for a window assigned to all desks"/>
</event>
<!-- Version 23 additions -->
<request name="set_initial_workspace" since="23">
<description summary="initial workspace for restore">
If |initial_workspace| equals '-1', a window is restored and visible on all workspaces,
Otherwise, set the initial workspace to restore the window to the corresponding workspace.
This is not double buffered and must be set before attaching a buffer.
</description>
<arg name="initial_workspace" type="string" summary="intial workspace for restoring or '-1' for visible on all workspaces"/>
</request>
<!-- Version 25 additions -->
<request name="set_pin" since="25">
<description summary="pin a window (trusted or not)">
Requests that a window is pinned which means that the system does not allow
the user to leave the window until an exit criteria is met.
This is a request to get the window pinned so that the user cannot get to any
other window / application. There are two modes:
A. trusted is 0 - which is slightly less restrictive and allows the user to
get out of the window by a predefined way of authentication.
B. trusted is not 0 in which case a trusted application was locking down the
system and needs to unlock. This is used for e.g. School tests.
</description>
<arg name="trusted" type="int" summary="0 for non trusted"/>
</request>
<request name="unset_pin" since="25">
<description summary="unpin a window">
Requests that the user can leave a previously pinned window.
This is a request to unpin a previously pinned window. It does not matter if
the window was locked with the trusted state or not.
</description>
</request>
<event name="start_throttle" since="29">
<description summary="start throttling on the surface">
Informs the client to start throttling on the surface.
</description>
</event>
<event name="end_throttle" since="29">
<description summary="end throttling on the surface">
Informs the client to end throttling on the surface.
</description>
</event>
<!-- Version 38 additions -->
<request name="release" type="destructor" since="38">
<description summary="destroy zaura_surface">
Destroy the zaura_surface object. A client should destroy this object when the
role is unmapped from a wl_surface.
See zaura_shell.release for destructor naming.
</description>
</request>
</interface>
<interface name="zaura_output" version="6">
<interface name="zaura_output" version="38">
<description summary="aura shell interface to a wl_output">
An additional interface to a wl_output object, which allows the
client to access aura shell specific functionality for output.
@ -233,6 +638,270 @@
</description>
<arg name="scale" type="uint" enum="scale_factor" summary="output device scale factor"/>
</event>
<!-- Version 33 additions -->
<event name="insets" since="33">
<description summary="advertise the insets for the output">
This event describes the insets for the output in logical screen
coordinates, from which the work area can be calculated.
This event is sent before wl_output.done, after which the client would
apply the change.
</description>
<arg name="top" type="int"/>
<arg name="left" type="int"/>
<arg name="bottom" type="int"/>
<arg name="right" type="int"/>
</event>
<!-- Version 34 additions -->
<event name="logical_transform" since="34">
<description summary="advertise the output's logical transform">
This event describes the logical transform for the output. Whereas
wl_output.geometry's transform corresponds to the display's panel
rotation, the logical transform corresponds to the display's logical
rotation.
This event is sent before wl_output.done, after which the client would
apply the change.
</description>
<arg name="transform" type="int" enum="wl_output.transform"/>
</event>
<!-- Version 38 additions -->
<request name="release" type="destructor" since="38">
<description summary="destroy zaura_output">
Destroy this zaura_shell object.
Destroying a bound zaura_shell object while there are zaura_surfaces
still alive created by this zaura_shell object instance is illegal
and will result in a protocol error.
See zaura_shell.release for destructor naming.
</description>
</request>
</interface>
<interface name="zaura_toplevel" version="38">
<description summary="aura shell interface to the toplevel shell">
An interface to the toplevel shell, which allows the
client to access shell specific functionality.
</description>
<enum name="orientation_lock">
<description summary="orientation lock request">
Defines orientation request when a surface is in fullscreen.
</description>
<entry name="none" value="1" summary="no orientation lock"/>
<entry name="portrait" value="2" summary="primary or secondary portrait"/>
<entry name="landscape" value="3" summary="primary or secondary landscape"/>
<entry name="current" value="4" summary="keep current orientation"/>
<entry name="portrait_primary" value="5" summary="primary portrait"/>
<entry name="landscape_primary" value="6" summary="primary landscape"/>
<entry name="portrait_secondary" value="7" summary="secondary portrait"/>
<entry name="landscape_secondary" value="8" summary="secondary landscape"/>
</enum>
<request name="set_orientation_lock" since="26">
<description summary="set orientation lock for a remote surface">
Request a specific orientation behavior when this surface is in fullscreen.
</description>
<arg name="orientation_lock" type="uint" enum="orientation_lock"/>
</request>
<request name="surface_submission_in_pixel_coordinates" since="28">
<description summary="surface will be submitted in pixel coordinates">
Informs the server that when submitting this surface, this client will not
use wl_surface_set_buffer_scale to report the scales, nor will it apply
scale via vp_viewporter. Instead the server should apply an appropriate
scale transform for the submitted buffers to be composited correctly.
</description>
</request>
<request name="set_supports_screen_coordinates" since="29">
<description summary="enables screen coordinates in window bounds">
Requesting this will enable screen coordinates in surfaces
associated with aura_toplevel including sub surfaces and popup
windows who added this toplevel as a parent. This should be
set before first commit.
</description>
</request>
<request name="set_window_bounds" since="29">
<description summary="set window size and position">
Request a new location and bounds of the surface in DP screen
coordinates. The size will be applied to visible bounds used
in set_geometry. The output is a hint for the compositor to
determine which output the window should move to. These
parameters are just a request and the compositor may ignore,
adjust the size and position based on the rule imposed by the
window manager, or may preserve it for future operations. For
example, the compositor will not allow a position outside of
the output, or the compositor may just store it if the
toplevel surface is in maximiezd state, and may use it upon
unmaximized.
</description>
<arg name="x" type="int"/>
<arg name="y" type="int"/>
<arg name="width" type="int"/>
<arg name="height" type="int"/>
<arg name="output" type="object" interface="wl_output" summary="the output"/>
</request>
<event name="configure" since="29">
<description summary="suggest a surface change">
A configuration change that also includes the window origin in screen coordinates.
</description>
<arg name="x" type="int"/>
<arg name="y" type="int"/>
<arg name="width" type="int"/>
<arg name="height" type="int"/>
<arg name="states" type="array"/>
</event>
<enum name="state">
<description summary="supplemental aura states to xdg states">
The states that are contained here are supplemental to the states
defined in the XDG shell and specific aura windows.
</description>
<!-- Offset by 100 to prevent collision with new XDG states. -->
<entry name="immersive" value="100" since="36">
<description summary="immersive mode with hidden title bar and shelf">
User can access system UIs such as the shelf and window frame
by pointing to, or swiping over, the screen edge.
</description>
</entry>
<entry name="minimized" value="101" since="36">
<description summary="surface is minized">
The window has been minimized.
</description>
</entry>
</enum>
<event name="origin_change" since="29">
<description summary="window origin change">
A notification sent when the window origin has changed. Unlike a configure,
this does not imply the client needs to resize. The values are in screen
coordinates.
</description>
<arg name="x" type="int" />
<arg name="y" type="int" />
</event>
<request name="set_restore_info" since="30">
<description summary="set session id and restore id">
Request session id and restore id of a newly created browser window.
Set the information used by compositor to restore the toplevel
surface state, such as window position, window state, upon creation.
This is not double buffered and must be set before sending first commit.
</description>
<arg name="restore_session_id" type="int" summary="unique browser session id"/>
<arg name="restore_window_id" type="int" summary="restore browser window id"/>
</request>
<!-- Version 31 additions -->
<request name="set_system_modal" since="31">
<description summary="make window a system modal">
Requests that the toplevel surface should become a system modal. The
compositor will prevent other windows from receiving events. If there
are multiple system modal surfaces, the compositor will decide which
one to receive events.
</description>
</request>
<request name="unset_system_modal" since="31">
<description summary="unset window system modal state">
Requests that the system modal state of the toplevel surface will be
unset. The compositor will then allow other windows to recieve events.
</description>
</request>
<request name="set_restore_info_with_window_id_source" since="32">
<description summary="set session id and restore window id source">
Request session id and restore id of the window. Set the information used by compositor to restore the toplevel surface state, such as window position, window state, upon creation. This is not double buffered and must be set before sending first commit. This is different from set_restore_info, used for clients that create multiple windows associated with restore_id_source.
</description>
<arg name="restore_session_id" type="int" summary="unique browser session id"/>
<arg name="restore_window_id_source" type="string" summary="restore window id source"/>
</request>
<request name="set_decoration" since="35">
<description summary="request a decoration for surface">
Clients are allowed to request a particular decoration for a
zaura_toplevel. The server is not required to honor this request. See
decoration_type for available options. Available since M105.
</description>
<arg name="type" type="uint" summary="the new frame type"/>
</request>
<enum name="decoration_type">
<description summary="different decoration types">
Decoration types are used to modify the surface (e.g. drop shadow).
</description>
<entry name="none" value="0" summary="no frame"/>
<entry name="normal" value="1" summary="caption with shadow"/>
<entry name="shadow" value="2" summary="shadow only"/>
</enum>
<!-- Version 38 additions -->
<request name="release" type="destructor" since="38">
<description summary="destroy zaura_toplevel">
Destroy this zaura_toplevel object. A client should call destroy when the role
is unmapped from a wl_surface.
See zaura_shell.release for destructor naming.
</description>
</request>
</interface>
<interface name="zaura_popup" version="38">
<description summary="aura shell interface to the popup shell">
An interface to the popup shell, which allows the
client to access shell specific functionality.
</description>
<request name="surface_submission_in_pixel_coordinates" since="28">
<description summary="surface will be submitted in pixel coordinates">
Informs the server that when submitting this surface, this client will not
use wl_surface_set_buffer_scale to report the scales, nor will it apply
scale via vp_viewporter. Instead the server should apply an appropriate
scale transform for the submitted buffers to be composited correctly.
</description>
</request>
<request name="set_decoration" since="35">
<description summary="request a decoration for surface">
Clients are allowed to request a particular decoration for a
zaura_toplevel. The server is not required to honor this request. See
decoration_type for available options. Available since M105.
</description>
<arg name="type" type="uint" summary="the new frame type"/>
</request>
<enum name="decoration_type">
<description summary="different decoration types">
Decoration types are used to modify the surface (e.g. drop shadow).
</description>
<entry name="none" value="0" summary="no frame"/>
<entry name="normal" value="1" summary="caption with shadow"/>
<entry name="shadow" value="2" summary="shadow only"/>
</enum>
<request name="set_menu" since="37">
<description summary="set popup type to menu">
Set popup type to menu
</description>
</request>
<!-- Version 38 additions -->
<request name="release" type="destructor" since="38">
<description summary="destroy zaura_popup">
This request destroys the zaura_popup. A client should call destroy when the
role is unmapped from a wl_surface.
See zaura_shell.release for destructor naming.
</description>
</request>
</interface>
</protocol>

View File

@ -0,0 +1,244 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="gaming_input_unstable_v2">
<copyright>
Copyright 2016 The Chromium Authors
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next
paragraph) shall be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
</copyright>
<interface name="zcr_gaming_input_v2" version="2">
<description summary="extends wl_seat with gaming input devices">
A global interface to provide gaming input devices for a given seat.
Currently only gamepad devices are supported.
Warning! The protocol described in this file is experimental and
backward incompatible changes may be made. Backward compatible changes
may be added together with the corresponding uinterface version bump.
Backward incompatible changes are done by bumping the version number in
the protocol and uinterface names and resetting the interface version.
Once the protocol is to be declared stable, the 'z' prefix and the
version number in the protocol and interface names are removed and the
interface version number is reset.
</description>
<request name="get_gaming_seat">
<description summary="get a gaming seat">
Get a gaming seat object for a given seat. Gaming seat provides access
to gaming devices
</description>
<arg name="gaming_seat" type="new_id" interface="zcr_gaming_seat_v2"/>
<arg name="seat" type="object" interface="wl_seat"/>
</request>
<request name="destroy" type="destructor">
<description summary="release the memory for the gaming input object">
Destroy gaming_input object. Objects created from this object are
unaffected and should be destroyed separately.
</description>
</request>
</interface>
<interface name="zcr_gaming_seat_v2" version="1">
<description summary="controller object for all gaming devices of a seat">
An object that provides access to all the gaming devices of a seat.
When a gamepad is connected, the compositor will send gamepad_added event.
</description>
<request name="destroy" type="destructor">
<description summary="release the memory for the gaming seat object">
Destroy gaming_seat object. Objects created from this object are
unaffected and should be destroyed separately.
</description>
</request>
<event name="gamepad_added">
<description summary="gamepad added event">
Notification that there is gamepad connected at this seat.
</description>
<arg name="gamepad" type="new_id" interface="zcr_gamepad_v2" summary="new connected gamepad"/>
</event>
<enum name="bus_type">
<description summary="gamepad device bus type">
Device connection type e.g. Bluetooth
</description>
<entry name="usb" value="0" summary="Universal Serial Bus" />
<entry name="bluetooth" value="1" summary="Bluetooth" />
</enum>
<event name="gamepad_added_with_device_info">
<description summary="gamepad added event">
Notification that there is gamepad connected at this seat.
</description>
<arg name="gamepad" type="new_id" interface="zcr_gamepad_v2" summary="new connected gamepad"/>
<arg name="name" type="string" summary="name of the gamepad device" />
<arg name="bus" type="uint" enum="bus_type" summary="type of the device connection e.g. Bluetooth" />
<arg name="vendor_id" type="uint" summary="vendor ID of the gamepad device" />
<arg name="product_id" type="uint" summary="product ID of the gamepad device" />
<arg name="version" type="uint" summary="product version of the gamepad device" />
</event>
</interface>
<interface name="zcr_gamepad_v2" version="2">
<description summary="gamepad input device">
The zcr_gamepad_v2 interface represents one or more gamepad input devices,
which are reported as a normalized 'Standard Gamepad' as it is specified
by the W3C Gamepad API at: https://w3c.github.io/gamepad/#remapping
</description>
<request name="destroy" type="destructor">
<description summary="destroy gamepad">
Destroy gamepad. Instances created from this gamepad are unaffected
and should be destroyed separately.
</description>
</request>
<event name="removed">
<description summary="gamepad removed">
Removed event is send when the gamepad is disconnected. The client should
expect no more event and call destroy.
This event cannot be used as destructor as requests (e.g. vibration) might
be added to this interface.
</description>
</event>
<event name="axis">
<description summary="axis change event">
Notification of axis change.
The axis id specifies which axis has changed as defined by the W3C
'Standard Gamepad'.
The value is calibrated and normalized to the -1 to 1 range.
</description>
<arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
<arg name="axis" type="uint" summary="axis that produced this event"/>
<arg name="value" type="fixed" summary="new value of axis"/>
</event>
<enum name="button_state">
<description summary="physical button state">
Describes the physical state of a button that produced the button
event.
</description>
<entry name="released" value="0" summary="the button is not pressed"/>
<entry name="pressed" value="1" summary="the button is pressed"/>
</enum>
<event name="button">
<description summary="Gamepad button changed">
Notification of button change.
The button id specifies which button has changed as defined by the W3C
'Standard Gamepad'.
A button can have a digital and an analog value. The analog value is
normalized to a 0 to 1 range.
If a button does not provide an analog value, it will be derived from
the digital state.
</description>
<arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
<arg name="button" type="uint" summary="id of button"/>
<arg name="state" type="uint" enum="button_state" summary="digital state of the button"/>
<arg name="analog" type="fixed" summary="analog value of the button"/>
</event>
<event name="frame">
<description summary="Notifies end of a series of gamepad changes.">
Indicates the end of a set of events that logically belong together.
A client is expected to accumulate the data in all events within the
frame before proceeding.
</description>
<arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
</event>
<event name="axis_added">
<description summary="an axis is added">
Adds an axis to the gamepad. Only called when the gamepad was created by
gamepad_added_with_device_info. The values are compatible with
input_absinfo.
</description>
<arg name="index" type="uint" summary="An index of the axis" />
<arg name="min_value" type="int" summary="minimum value of the axis" />
<arg name="max_value" type="int" summary="maximum value of the axis" />
<arg name="flat" type="int" summary="input within this value are ignored" />
<arg name="fuzz" type="int" summary="used to filter noise" />
<arg name="resolution" type="int" summary="resolution of input in units per millimeter, or units per radian for rotational axes." />
</event>
<event name="activated">
<description summary="Gamepad activated">
Activates the gamepad i.e. the gamepad will be visible to applications
after this event is fired. All axis_added events should be sent before
this event. Only called when the gamepad was created by
gamepad_added_with_device_info.
</description>
</event>
<!-- added since v2 -->
<event name="vibrator_added" since="2">
<description summary="a vibrator is added">
Adds a vibrator to the gamepad. Only called if server has verified
that gamepad has a vibrator. The vibrator(s) for a gamepad are expected
to be added before the "activated" event is called.
</description>
<arg name="vibrator" type="new_id" interface="zcr_gamepad_vibrator_v2" summary="the gamepad vibrator"/>
</event>
</interface>
<interface name="zcr_gamepad_vibrator_v2" version="2">
<description summary="vibrator interface for a gamepad">
An interface that provides access to the vibrator of a gamepad. Requests can be
sent to make the gamepad vibrate and to stop an ongoing vibration.
</description>
<request name="vibrate" since="2">
<description summary="triggers the vibration event">
Triggers the vibration event on the gamepad vibrator. The gamepad is only allowed to
vibrate while the window is in focus. The values in the timings array are 64-bit integers
and the values in the amplitudes array are unsigned 8-bit integers.
The timings array and the amplitudes array are of the same length.
For each timing/amplitude pair, the amplitude determines the strength of
the vibration and the timing determines the length of the vibration in milliseconds.
Amplitude values must be between 0 and 255. An amplitude of 0 implies no vibration
and any timing/amplitude pair with a timing value of 0 is ignored.
The repeat argument determines the index at which the vibration pattern to repeat begins.
A repeat value of -1 disables repetition. If repetition is enabled, the vibration
pattern will repeat indefinitely until stopped, or when focus is lost.
</description>
<arg name="timings" type="array" summary="array of timing values" />
<arg name="amplitudes" type="array" summary="array of amplitude values" />
<arg name="repeat" type="int" summary="index into the timings array at which to repeat" />
</request>
<request name="cancel_vibration" since="2">
<description summary="cancels the existing vibration event">
Cancels the currently ongoing vibration event on the gamepad vibrator.
</description>
</request>
<request name="destroy" type="destructor" since="2">
<description summary="destroy gamepad vibrator"/>
</request>
</interface>
</protocol>

View File

@ -2,7 +2,7 @@
<protocol name="keyboard_extension_unstable_v1">
<copyright>
Copyright 2017 The Chromium Authors.
Copyright 2017 The Chromium Authors
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),

View File

@ -0,0 +1,256 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="zwp_linux_explicit_synchronization_unstable_v1">
<copyright>
Copyright 2016 The Chromium Authors
Copyright 2017 Intel Corporation
Copyright 2018 Collabora, Ltd
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next
paragraph) shall be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
</copyright>
<interface name="zwp_linux_explicit_synchronization_v1" version="2">
<description summary="protocol for providing explicit synchronization">
This global is a factory interface, allowing clients to request
explicit synchronization for buffers on a per-surface basis.
See zwp_linux_surface_synchronization_v1 for more information.
This interface is derived from Chromium's
zcr_linux_explicit_synchronization_v1.
Warning! The protocol described in this file is experimental and
backward incompatible changes may be made. Backward compatible changes
may be added together with the corresponding interface version bump.
Backward incompatible changes are done by bumping the version number in
the protocol and interface names and resetting the interface version.
Once the protocol is to be declared stable, the 'z' prefix and the
version number in the protocol and interface names are removed and the
interface version number is reset.
</description>
<request name="destroy" type="destructor">
<description summary="destroy explicit synchronization factory object">
Destroy this explicit synchronization factory object. Other objects,
including zwp_linux_surface_synchronization_v1 objects created by this
factory, shall not be affected by this request.
</description>
</request>
<enum name="error">
<entry name="synchronization_exists" value="0"
summary="the surface already has a synchronization object associated"/>
</enum>
<request name="get_synchronization">
<description summary="extend surface interface for explicit synchronization">
Instantiate an interface extension for the given wl_surface to provide
explicit synchronization.
If the given wl_surface already has an explicit synchronization object
associated, the synchronization_exists protocol error is raised.
Graphics APIs, like EGL or Vulkan, that manage the buffer queue and
commits of a wl_surface themselves, are likely to be using this
extension internally. If a client is using such an API for a
wl_surface, it should not directly use this extension on that surface,
to avoid raising a synchronization_exists protocol error.
</description>
<arg name="id" type="new_id"
interface="zwp_linux_surface_synchronization_v1"
summary="the new synchronization interface id"/>
<arg name="surface" type="object" interface="wl_surface"
summary="the surface"/>
</request>
</interface>
<interface name="zwp_linux_surface_synchronization_v1" version="2">
<description summary="per-surface explicit synchronization support">
This object implements per-surface explicit synchronization.
Synchronization refers to co-ordination of pipelined operations performed
on buffers. Most GPU clients will schedule an asynchronous operation to
render to the buffer, then immediately send the buffer to the compositor
to be attached to a surface.
In implicit synchronization, ensuring that the rendering operation is
complete before the compositor displays the buffer is an implementation
detail handled by either the kernel or userspace graphics driver.
By contrast, in explicit synchronization, dma_fence objects mark when the
asynchronous operations are complete. When submitting a buffer, the
client provides an acquire fence which will be waited on before the
compositor accesses the buffer. The Wayland server, through a
zwp_linux_buffer_release_v1 object, will inform the client with an event
which may be accompanied by a release fence, when the compositor will no
longer access the buffer contents due to the specific commit that
requested the release event.
Each surface can be associated with only one object of this interface at
any time.
In version 1 of this interface, explicit synchronization is only
guaranteed to be supported for buffers created with any version of the
wp_linux_dmabuf buffer factory. Version 2 additionally guarantees
explicit synchronization support for opaque EGL buffers, which is a type
of platform specific buffers described in the EGL_WL_bind_wayland_display
extension. Compositors are free to support explicit synchronization for
additional buffer types.
</description>
<request name="destroy" type="destructor">
<description summary="destroy synchronization object">
Destroy this explicit synchronization object.
Any fence set by this object with set_acquire_fence since the last
commit will be discarded by the server. Any fences set by this object
before the last commit are not affected.
zwp_linux_buffer_release_v1 objects created by this object are not
affected by this request.
</description>
</request>
<enum name="error">
<entry name="invalid_fence" value="0"
summary="the fence specified by the client could not be imported"/>
<entry name="duplicate_fence" value="1"
summary="multiple fences added for a single surface commit"/>
<entry name="duplicate_release" value="2"
summary="multiple releases added for a single surface commit"/>
<entry name="no_surface" value="3"
summary="the associated wl_surface was destroyed"/>
<entry name="unsupported_buffer" value="4"
summary="the buffer does not support explicit synchronization"/>
<entry name="no_buffer" value="5"
summary="no buffer was attached"/>
</enum>
<request name="set_acquire_fence">
<description summary="set the acquire fence">
Set the acquire fence that must be signaled before the compositor
may sample from the buffer attached with wl_surface.attach. The fence
is a dma_fence kernel object.
The acquire fence is double-buffered state, and will be applied on the
next wl_surface.commit request for the associated surface. Thus, it
applies only to the buffer that is attached to the surface at commit
time.
If the provided fd is not a valid dma_fence fd, then an INVALID_FENCE
error is raised.
If a fence has already been attached during the same commit cycle, a
DUPLICATE_FENCE error is raised.
If the associated wl_surface was destroyed, a NO_SURFACE error is
raised.
If at surface commit time the attached buffer does not support explicit
synchronization, an UNSUPPORTED_BUFFER error is raised.
If at surface commit time there is no buffer attached, a NO_BUFFER
error is raised.
</description>
<arg name="fd" type="fd" summary="acquire fence fd"/>
</request>
<request name="get_release">
<description summary="release fence for last-attached buffer">
Create a listener for the release of the buffer attached by the
client with wl_surface.attach. See zwp_linux_buffer_release_v1
documentation for more information.
The release object is double-buffered state, and will be associated
with the buffer that is attached to the surface at wl_surface.commit
time.
If a zwp_linux_buffer_release_v1 object has already been requested for
the surface in the same commit cycle, a DUPLICATE_RELEASE error is
raised.
If the associated wl_surface was destroyed, a NO_SURFACE error
is raised.
If at surface commit time there is no buffer attached, a NO_BUFFER
error is raised.
</description>
<arg name="release" type="new_id" interface="zwp_linux_buffer_release_v1"
summary="new zwp_linux_buffer_release_v1 object"/>
</request>
</interface>
<interface name="zwp_linux_buffer_release_v1" version="1">
<description summary="buffer release explicit synchronization">
This object is instantiated in response to a
zwp_linux_surface_synchronization_v1.get_release request.
It provides an alternative to wl_buffer.release events, providing a
unique release from a single wl_surface.commit request. The release event
also supports explicit synchronization, providing a fence FD for the
client to synchronize against.
Exactly one event, either a fenced_release or an immediate_release, will
be emitted for the wl_surface.commit request. The compositor can choose
release by release which event it uses.
This event does not replace wl_buffer.release events; servers are still
required to send those events.
Once a buffer release object has delivered a 'fenced_release' or an
'immediate_release' event it is automatically destroyed.
</description>
<event name="fenced_release">
<description summary="release buffer with fence">
Sent when the compositor has finalised its usage of the associated
buffer for the relevant commit, providing a dma_fence which will be
signaled when all operations by the compositor on that buffer for that
commit have finished.
Once the fence has signaled, and assuming the associated buffer is not
pending release from other wl_surface.commit requests, no additional
explicit or implicit synchronization is required to safely reuse or
destroy the buffer.
This event destroys the zwp_linux_buffer_release_v1 object.
</description>
<arg name="fence" type="fd" summary="fence for last operation on buffer"/>
</event>
<event name="immediate_release">
<description summary="release buffer immediately">
Sent when the compositor has finalised its usage of the associated
buffer for the relevant commit, and either performed no operations
using it, or has a guarantee that all its operations on that buffer for
that commit have finished.
Once this event is received, and assuming the associated buffer is not
pending release from other wl_surface.commit requests, no additional
explicit or implicit synchronization is required to safely reuse or
destroy the buffer.
This event destroys the zwp_linux_buffer_release_v1 object.
</description>
</event>
</interface>
</protocol>

View File

@ -0,0 +1,263 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="text_input_extension_unstable_v1">
<copyright>
Copyright 2021 The Chromium Authors
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next
paragraph) shall be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
</copyright>
<interface name="zcr_text_input_extension_v1" version="4">
<description summary="extends text_input to support richer operations">
Allows a text_input to sends more variation of operations to support
richer features, such as set_preedit_region.
Warning! The protocol described in this file is experimental and
backward incompatible changes may be made. Backward compatible changes
may be added together with the corresponding uinterface version bump.
Backward incompatible changes are done by bumping the version number in
the protocol and interface names and resetting the interface version.
Once the protocol is to be declared stable, the 'z' prefix and the
version number in the protocol and interface names are removed and the
interface version number is reset.
</description>
<enum name="error">
<entry name="extended_text_input_exists" value="0"
summary="the text_input already has an extended_text_input object associated"/>
</enum>
<request name="get_extended_text_input">
<description summary="get extended_text_input for a text_input">
Create extended_text_input object.
See zcr_extended_text_input interface for details.
If the given text_input object already has a extended_text_input object
associated, the extended_text_input_exists protocol error is raised.
</description>
<arg name="id" type="new_id" interface="zcr_extended_text_input_v1"/>
<arg name="text_input" type="object" interface="zwp_text_input_v1"/>
</request>
</interface>
<interface name="zcr_extended_text_input_v1" version="4">
<description summary="extension of text_input protocol">
The zcr_extended_text_input_v1 interface extends the text_input interface
to support more rich operations on text_input.
</description>
<request name="destroy" type="destructor">
<description summary="destroy extended_text_input object"/>
</request>
<event name="set_preedit_region">
<description summary="set preedit from the surrounding text">
IME requests to update text_input client to set the preedit
from the surrounding text.
index is the starting point of the preedit, relative to the current
cursor position in utf-8 byte-offset.
length is the length of the preedit region in utf-8 byte length.
Following the convention we have in text_input::preedit_string,
text_input::preedit_styling sent just before this will be applied.
</description>
<arg name="index" type="int" />
<arg name="length" type="uint" />
</event>
<!-- Version 2 -->
<enum name="input_type" since="2">
<description summary="Chrome's TextInputType">
Wayland has its own input-type support, which is
zwp_text_input::content_purpose. However, it is not rich enough to
represent all Chrome's input types. This enum is introduced to keep
all entries so exo can understand it without any information loss.
See TextInputType's description for details about each entry.
</description>
<entry name="none" value="0" />
<entry name="text" value="1" />
<entry name="password" value="2" />
<entry name="search" value="3" />
<entry name="email" value="4" />
<entry name="number" value="5" />
<entry name="telephone" value="6" />
<entry name="url" value="7" />
<entry name="date" value="8" />
<entry name="date_time" value="9" />
<entry name="date_time_local" value="10" />
<entry name="month" value="11" />
<entry name="time" value="12" />
<entry name="week" value="13" />
<entry name="text_area" value="14" />
<entry name="content_editable" value="15" />
<entry name="date_time_field" value="16" />
<entry name="null" value="17" />
</enum>
<enum name="input_mode" since="2">
<description summary="Chrome's TextInputMode">
Similar to input_type defined above, this keeps Chrome's TextInputMode.
See TextInputMode's description for details for each entry.
</description>
<entry name="default" value="0" />
<entry name="none" value="1" />
<entry name="text" value="2" />
<entry name="tel" value="3" />
<entry name="url" value="4" />
<entry name="email" value="5" />
<entry name="numeric" value="6" />
<entry name="decimal" value="7" />
<entry name="search" value="8" />
</enum>
<enum name="input_flags" since="2">
<description summary="Chrome's TextInputFlags">
Similar to input_type defined above, this keeps Chrome's TextInputFlags,
because content_hint is not enough power to represent what Chrome wants.
See TextInputFlags' description for details for each entry.
</description>
<entry name="none" value="0" />
<entry name="autocomplete_on" value="1 &lt;&lt; 0" />
<entry name="autocomplete_off" value="1 &lt;&lt; 1" />
<entry name="autocorrect_on" value="1 &lt;&lt; 2" />
<entry name="autocorrect_off" value="1 &lt;&lt; 3" />
<entry name="spellcheck_on" value="1 &lt;&lt; 4" />
<entry name="spellcheck_off" value="1 &lt;&lt; 5" />
<entry name="autocapitalize_none" value="1 &lt;&lt; 6" />
<entry name="autocapitalize_characters" value="1 &lt;&lt; 7" />
<entry name="autocapitalize_words" value="1 &lt;&lt; 8" />
<entry name="autocapitalize_sentences" value="1 &lt;&lt; 9" />
<entry name="has_been_password" value="1 &lt;&lt; 10" />
</enum>
<enum name="learning_mode" since="2">
<description summary="Whether IME is allowed to learn" />
<entry name="disabled" value="0" />
<entry name="enabled" value="1" />
</enum>
<request name="set_input_type" since="2">
<description summary="Sets input type, mode and flags together">
In wayland, there's a concept that can be mapped to Chrome's
TextInputType, Mode and Flags. It can be set via
zwp_text_input::set_content_type. However, the variation is not rich
enough to represent Chrome's detailed behavior change. This API can be
used as a replacement of set_content_type.
</description>
<arg name="input_type" type="uint" enum="input_type" />
<arg name="input_mode" type="uint" enum="input_mode" />
<arg name="input_flags" type="uint" />
<arg name="learning_mode" type="uint" enum="learning_mode" />
</request>
<!-- Version 3 -->
<event name="clear_grammar_fragments" since="3">
<description summary="clear grammar fragments in a range">
IME requests to clear all the grammar markers within the given range
defined by start and end.
start and end are relative to the beginning of the input field in
utf-8 byte length.
</description>
<arg name="start" type="uint" />
<arg name="end" type="uint" />
</event>
<event name="add_grammar_fragment" since="3">
<description summary="add grammar fragment">
IME requests to add a new grammar fragment.
A grammar fragment describes a range of text (start, end) that has
grammar error and also gives the correct replacement text. It is
expected that the renderer will render markers (e.g. squigles or dashed
underlines) under the text to notify users that there is a grammar
error. It is also expected that the renderer will maintain and update
the position of fragment when users edit other parts of the text, e.g.
if users type something before the grammar fragment, the marker should
move accordingly.
start and end are relative to the beginning of the input field in
utf-8 byte length. suggestion is the correct replacement text, encoded
in utf-8 and suggested by ML model.
</description>
<arg name="start" type="uint" />
<arg name="end" type="uint" />
<arg name="suggestion" type="string" />
</event>
<request name="set_grammar_fragment_at_cursor" since="3">
<description summary="add grammar fragment">
Informs the IME of the grammar fragment containing the current cursor.
If not existing, both start and end are set to 0. This is called
whenever the cursor position or surrounding text have changed.
start and end are relative to the beginning of the input field in
utf-8 byte length. suggestion is the correct replacement text encoded
in utf-8 and suggested by ML model.
</description>
<arg name="start" type="uint" />
<arg name="end" type="uint" />
<arg name="suggestion" type="string" />
</request>
<!-- Version 4 -->
<event name="set_autocorrect_range" since="4">
<description summary="set autocorrect range">
IME requests to update text_input client to set the autocorrect range.
There is only one autocorrect range, so this replaces any existing
autocorrect ranges.
start and end are relative to the beginning of the input field in utf-8
byte length.
If start and end are the same, then the autocorrect range is cleared.
</description>
<arg name="start" type="uint" />
<arg name="end" type="uint" />
</event>
<request name="set_autocorrect_info" since="4">
<description summary="set autocorrect range">
Informs the IME the range and bounds of the current autocorrect change.
This is called whenever the range or bounds have changed.
start and end are relative to the beginning of the input field in utf-8
byte length.
x, y, width, and height are the bounds of the autocorrect text, relative
to the window.
This request only changes a pending state that will be effective on the
next 'set_surrounding_text' request.
</description>
<arg name="start" type="uint" />
<arg name="end" type="uint" />
<arg name="x" type="uint" />
<arg name="y" type="uint" />
<arg name="width" type="uint" />
<arg name="height" type="uint" />
</request>
</interface>
</protocol>

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="text_input_x11_unstable_v1">
<copyright>
Copyright 2022 The ChromiumOS Authors
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
</copyright>
<interface name="zcr_text_input_x11_v1" version="1">
<description summary="Extension support for using text_input with X11 apps">
Adds functionality for X11 apps running on Xwayland to connect to the
Wayland compositor and receive text_input support.
</description>
<request name="activate">
<description summary="request activation">
Calls text_input::activate with the X11 window id converted to the
matching wl_surface.
</description>
<arg name="text_input" type="object" interface="zwp_text_input_v1"/>
<arg name="seat" type="object" interface="wl_seat"/>
<arg name="x11_window_id" type="uint"/>
</request>
</interface>
</protocol>

View File

@ -0,0 +1,220 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="xdg_output_unstable_v1">
<copyright>
Copyright © 2017 Red Hat Inc.
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next
paragraph) shall be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
</copyright>
<description summary="Protocol to describe output regions">
This protocol aims at describing outputs in a way which is more in line
with the concept of an output on desktop oriented systems.
Some information are more specific to the concept of an output for
a desktop oriented system and may not make sense in other applications,
such as IVI systems for example.
Typically, the global compositor space on a desktop system is made of
a contiguous or overlapping set of rectangular regions.
Some of the information provided in this protocol might be identical
to their counterparts already available from wl_output, in which case
the information provided by this protocol should be preferred to their
equivalent in wl_output. The goal is to move the desktop specific
concepts (such as output location within the global compositor space,
the connector name and types, etc.) out of the core wl_output protocol.
Warning! The protocol described in this file is experimental and
backward incompatible changes may be made. Backward compatible
changes may be added together with the corresponding interface
version bump.
Backward incompatible changes are done by bumping the version
number in the protocol and interface names and resetting the
interface version. Once the protocol is to be declared stable,
the 'z' prefix and the version number in the protocol and
interface names are removed and the interface version number is
reset.
</description>
<interface name="zxdg_output_manager_v1" version="3">
<description summary="manage xdg_output objects">
A global factory interface for xdg_output objects.
</description>
<request name="destroy" type="destructor">
<description summary="destroy the xdg_output_manager object">
Using this request a client can tell the server that it is not
going to use the xdg_output_manager object anymore.
Any objects already created through this instance are not affected.
</description>
</request>
<request name="get_xdg_output">
<description summary="create an xdg output from a wl_output">
This creates a new xdg_output object for the given wl_output.
</description>
<arg name="id" type="new_id" interface="zxdg_output_v1"/>
<arg name="output" type="object" interface="wl_output"/>
</request>
</interface>
<interface name="zxdg_output_v1" version="3">
<description summary="compositor logical output region">
An xdg_output describes part of the compositor geometry.
This typically corresponds to a monitor that displays part of the
compositor space.
For objects version 3 onwards, after all xdg_output properties have been
sent (when the object is created and when properties are updated), a
wl_output.done event is sent. This allows changes to the output
properties to be seen as atomic, even if they happen via multiple events.
</description>
<request name="destroy" type="destructor">
<description summary="destroy the xdg_output object">
Using this request a client can tell the server that it is not
going to use the xdg_output object anymore.
</description>
</request>
<event name="logical_position">
<description summary="position of the output within the global compositor space">
The position event describes the location of the wl_output within
the global compositor space.
The logical_position event is sent after creating an xdg_output
(see xdg_output_manager.get_xdg_output) and whenever the location
of the output changes within the global compositor space.
</description>
<arg name="x" type="int"
summary="x position within the global compositor space"/>
<arg name="y" type="int"
summary="y position within the global compositor space"/>
</event>
<event name="logical_size">
<description summary="size of the output in the global compositor space">
The logical_size event describes the size of the output in the
global compositor space.
For example, a surface without any buffer scale, transformation
nor rotation set, with the size matching the logical_size will
have the same size as the corresponding output when displayed.
Most regular Wayland clients should not pay attention to the
logical size and would rather rely on xdg_shell interfaces.
Some clients such as Xwayland, however, need this to configure
their surfaces in the global compositor space as the compositor
may apply a different scale from what is advertised by the output
scaling property (to achieve fractional scaling, for example).
For example, for a wl_output mode 3840×2160 and a scale factor 2:
- A compositor not scaling the surface buffers will advertise a
logical size of 3840×2160,
- A compositor automatically scaling the surface buffers will
advertise a logical size of 1920×1080,
- A compositor using a fractional scale of 1.5 will advertise a
logical size of 2560×1440.
For example, for a wl_output mode 1920×1080 and a 90 degree rotation,
the compositor will advertise a logical size of 1080x1920.
The logical_size event is sent after creating an xdg_output
(see xdg_output_manager.get_xdg_output) and whenever the logical
size of the output changes, either as a result of a change in the
applied scale or because of a change in the corresponding output
mode(see wl_output.mode) or transform (see wl_output.transform).
</description>
<arg name="width" type="int"
summary="width in global compositor space"/>
<arg name="height" type="int"
summary="height in global compositor space"/>
</event>
<event name="done">
<description summary="all information about the output have been sent">
This event is sent after all other properties of an xdg_output
have been sent.
This allows changes to the xdg_output properties to be seen as
atomic, even if they happen via multiple events.
For objects version 3 onwards, this event is deprecated. Compositors
are not required to send it anymore and must send wl_output.done
instead.
</description>
</event>
<!-- Version 2 additions -->
<event name="name" since="2">
<description summary="name of this output">
Many compositors will assign names to their outputs, show them to the
user, allow them to be configured by name, etc. The client may wish to
know this name as well to offer the user similar behaviors.
The naming convention is compositor defined, but limited to
alphanumeric characters and dashes (-). Each name is unique among all
wl_output globals, but if a wl_output global is destroyed the same name
may be reused later. The names will also remain consistent across
sessions with the same hardware and software configuration.
Examples of names include 'HDMI-A-1', 'WL-1', 'X11-1', etc. However, do
not assume that the name is a reflection of an underlying DRM
connector, X11 connection, etc.
The name event is sent after creating an xdg_output (see
xdg_output_manager.get_xdg_output). This event is only sent once per
xdg_output, and the name does not change over the lifetime of the
wl_output global.
</description>
<arg name="name" type="string" summary="output name"/>
</event>
<event name="description" since="2">
<description summary="human-readable description of this output">
Many compositors can produce human-readable descriptions of their
outputs. The client may wish to know this description as well, to
communicate the user for various purposes.
The description is a UTF-8 string with no convention defined for its
contents. Examples might include 'Foocorp 11" Display' or 'Virtual X11
output via :1'.
The description event is sent after creating an xdg_output (see
xdg_output_manager.get_xdg_output) and whenever the description
changes. The description is optional, and may not be sent at all.
For objects of version 2 and lower, this event is only sent once per
xdg_output, and the description does not change over the lifetime of
the wl_output global.
</description>
<arg name="description" type="string" summary="output description"/>
</event>
</interface>
</protocol>

View File

@ -1,11 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="xdg_shell_unstable_v6">
<protocol name="xdg_shell">
<copyright>
Copyright © 2008-2013 Kristian Høgsberg
Copyright © 2013 Rafael Antognolli
Copyright © 2013 Jasper St. Pierre
Copyright © 2010-2013 Intel Corporation
Copyright © 2015-2017 Samsung Electronics Co., Ltd
Copyright © 2015-2017 Red Hat Inc.
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
@ -27,18 +29,19 @@
DEALINGS IN THE SOFTWARE.
</copyright>
<interface name="zxdg_shell_v6" version="1">
<interface name="xdg_wm_base" version="3">
<description summary="create desktop-style surfaces">
xdg_shell allows clients to turn a wl_surface into a "real window"
which can be dragged, resized, stacked, and moved around by the
user. Everything about this interface is suited towards traditional
desktop environments.
The xdg_wm_base interface is exposed as a global object enabling clients
to turn their wl_surfaces into windows in a desktop environment. It
defines the basic functionality needed for clients and the compositor to
create windows that can be dragged, resized, maximized, etc, as well as
creating transient windows such as popup menus.
</description>
<enum name="error">
<entry name="role" value="0" summary="given wl_surface has another role"/>
<entry name="defunct_surfaces" value="1"
summary="xdg_shell was destroyed before children"/>
summary="xdg_wm_base was destroyed before children"/>
<entry name="not_the_topmost_popup" value="2"
summary="the client tried to map or destroy a non-topmost popup"/>
<entry name="invalid_popup_parent" value="3"
@ -50,11 +53,11 @@
</enum>
<request name="destroy" type="destructor">
<description summary="destroy xdg_shell">
Destroy this xdg_shell object.
<description summary="destroy xdg_wm_base">
Destroy this xdg_wm_base object.
Destroying a bound xdg_shell object while there are surfaces
still alive created by this xdg_shell object instance is illegal
Destroying a bound xdg_wm_base object while there are surfaces
still alive created by this xdg_wm_base object instance is illegal
and will result in a protocol error.
</description>
</request>
@ -65,7 +68,7 @@
surfaces relative to some parent surface. See the interface description
and xdg_surface.get_popup for details.
</description>
<arg name="id" type="new_id" interface="zxdg_positioner_v6"/>
<arg name="id" type="new_id" interface="xdg_positioner"/>
</request>
<request name="get_xdg_surface">
@ -82,14 +85,14 @@
See the documentation of xdg_surface for more details about what an
xdg_surface is and how it is used.
</description>
<arg name="id" type="new_id" interface="zxdg_surface_v6"/>
<arg name="id" type="new_id" interface="xdg_surface"/>
<arg name="surface" type="object" interface="wl_surface"/>
</request>
<request name="pong">
<description summary="respond to a ping event">
A client must respond to a ping event with a pong request or
the client may be deemed unresponsive. See xdg_shell.ping.
the client may be deemed unresponsive. See xdg_wm_base.ping.
</description>
<arg name="serial" type="uint" summary="serial of the ping event"/>
</request>
@ -98,7 +101,7 @@
<description summary="check if the client is alive">
The ping event asks the client if it's still alive. Pass the
serial specified in the event back to the compositor by sending
a "pong" request back with the specified serial. See xdg_shell.ping.
a "pong" request back with the specified serial. See xdg_wm_base.pong.
Compositors can use this to determine if the client is still
alive. It's unspecified what will happen if the client doesn't
@ -106,13 +109,13 @@
try to respond in a reasonable amount of time.
A compositor is free to ping in any way it wants, but a client must
always respond to any xdg_shell object it created.
always respond to any xdg_wm_base object it created.
</description>
<arg name="serial" type="uint" summary="pass this to the pong request"/>
</event>
</interface>
<interface name="zxdg_positioner_v6" version="1">
<interface name="xdg_positioner" version="3">
<description summary="child surface positioner">
The xdg_positioner provides a collection of rules for the placement of a
child surface relative to a parent surface. Rules can be defined to ensure
@ -162,13 +165,13 @@
Specify the anchor rectangle within the parent surface that the child
surface will be placed relative to. The rectangle is relative to the
window geometry as defined by xdg_surface.set_window_geometry of the
parent surface. The rectangle must be at least 1x1 large.
parent surface.
When the xdg_positioner object is used to position a child surface, the
anchor rectangle may not extend outside the window geometry of the
positioned child's parent surface.
If a zero or negative size is set the invalid_input error is raised.
If a negative size is set the invalid_input error is raised.
</description>
<arg name="x" type="int" summary="x position of anchor rectangle"/>
<arg name="y" type="int" summary="y position of anchor rectangle"/>
@ -176,63 +179,54 @@
<arg name="height" type="int" summary="height of anchor rectangle"/>
</request>
<enum name="anchor" bitfield="true">
<entry name="none" value="0"
summary="the center of the anchor rectangle"/>
<entry name="top" value="1"
summary="the top edge of the anchor rectangle"/>
<entry name="bottom" value="2"
summary="the bottom edge of the anchor rectangle"/>
<entry name="left" value="4"
summary="the left edge of the anchor rectangle"/>
<entry name="right" value="8"
summary="the right edge of the anchor rectangle"/>
<enum name="anchor">
<entry name="none" value="0"/>
<entry name="top" value="1"/>
<entry name="bottom" value="2"/>
<entry name="left" value="3"/>
<entry name="right" value="4"/>
<entry name="top_left" value="5"/>
<entry name="bottom_left" value="6"/>
<entry name="top_right" value="7"/>
<entry name="bottom_right" value="8"/>
</enum>
<request name="set_anchor">
<description summary="set anchor rectangle anchor edges">
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.
<description summary="set anchor rectangle anchor">
Defines the anchor point for the anchor rectangle. The specified anchor
is used derive an anchor point that the child surface will be
positioned relative to. If a corner anchor is set (e.g. 'top_left' or
'bottom_right'), the anchor point will be at the specified corner;
otherwise, the derived anchor point will be centered on the specified
edge, or in the center of the anchor rectangle if no edge is specified.
</description>
<arg name="anchor" type="uint" enum="anchor"
summary="bit mask of anchor edges"/>
summary="anchor"/>
</request>
<enum name="gravity" bitfield="true">
<entry name="none" value="0"
summary="center over the anchor edge"/>
<entry name="top" value="1"
summary="position above the anchor edge"/>
<entry name="bottom" value="2"
summary="position below the anchor edge"/>
<entry name="left" value="4"
summary="position to the left of the anchor edge"/>
<entry name="right" value="8"
summary="position to the right of the anchor edge"/>
<enum name="gravity">
<entry name="none" value="0"/>
<entry name="top" value="1"/>
<entry name="bottom" value="2"/>
<entry name="left" value="3"/>
<entry name="right" value="4"/>
<entry name="top_left" value="5"/>
<entry name="bottom_left" value="6"/>
<entry name="top_right" value="7"/>
<entry name="bottom_right" value="8"/>
</enum>
<request name="set_gravity">
<description summary="set child surface gravity">
Defines in what direction a surface should be positioned, relative to
the anchor point of the parent surface. If two orthogonal gravities are
specified (e.g. 'bottom' and 'right'), then the child surface will be
placed in the specified direction; otherwise, the child surface will be
centered over the anchor point on any axis that had no gravity
specified.
If two parallel gravities are specified (e.g. 'left' and 'right'), the
invalid_input error is raised.
the anchor point of the parent surface. If a corner gravity is
specified (e.g. 'bottom_right' or 'top_left'), then the child surface
will be placed towards the specified gravity; otherwise, the child
surface will be centered over the anchor point on any axis that had no
gravity specified.
</description>
<arg name="gravity" type="uint" enum="gravity"
summary="bit mask of gravity directions"/>
summary="gravity direction"/>
</request>
<enum name="constraint_adjustment" bitfield="true">
@ -252,7 +246,7 @@
<entry name="none" value="0">
<description summary="don't move the child surface when constrained">
Don't alter the surface position even if it is constrained on some
axis, for example partially outside the edge of a monitor.
axis, for example partially outside the edge of an output.
</description>
</entry>
<entry name="slide_x" value="1">
@ -304,6 +298,10 @@
surface is constrained, the gravity is 'bottom' and the anchor is
'bottom', change the gravity to 'top' and the anchor to 'top'.
The adjusted position is calculated given the original anchor
rectangle and offset, but with the new flipped anchor and gravity
values.
If the adjusted position also ends up being constrained, the resulting
position of the flip_y adjustment will be the one before the
adjustment.
@ -359,9 +357,49 @@
<arg name="x" type="int" summary="surface position x offset"/>
<arg name="y" type="int" summary="surface position y offset"/>
</request>
<!-- Version 3 additions -->
<request name="set_reactive" since="3">
<description summary="continuously reconstrain the surface">
When set reactive, the surface is reconstrained if the conditions used
for constraining changed, e.g. the parent window moved.
If the conditions changed and the popup was reconstrained, an
xdg_popup.configure event is sent with updated geometry, followed by an
xdg_surface.configure event.
</description>
</request>
<request name="set_parent_size" since="3">
<description summary="">
Set the parent window geometry the compositor should use when
positioning the popup. The compositor may use this information to
determine the future state the popup should be constrained using. If
this doesn't match the dimension of the parent the popup is eventually
positioned against, the behavior is undefined.
The arguments are given in the surface-local coordinate space.
</description>
<arg name="parent_width" type="int"
summary="future window geometry width of parent"/>
<arg name="parent_height" type="int"
summary="future window geometry height of parent"/>
</request>
<request name="set_parent_configure" since="3">
<description summary="set parent configure this is a response to">
Set the serial of a xdg_surface.configure event this positioner will be
used in response to. The compositor may use this information together
with set_parent_size to determine what future state the popup should be
constrained using.
</description>
<arg name="serial" type="uint"
summary="serial of parent configure event"/>
</request>
</interface>
<interface name="zxdg_surface_v6" version="1">
<interface name="xdg_surface" version="3">
<description summary="desktop user interface surface base interface">
An interface that may be implemented by a wl_surface, for
implementations that provide a desktop-style user interface.
@ -388,11 +426,25 @@
manipulate a buffer prior to the first xdg_surface.configure call must
also be treated as errors.
For a surface to be mapped by the compositor, the following conditions
must be met: (1) the client has assigned a xdg_surface based role to the
surface, (2) the client has set and committed the xdg_surface state and
the role dependent state to the surface and (3) the client has committed a
buffer to the surface.
After creating a role-specific object and setting it up, the client must
perform an initial commit without any buffer attached. The compositor
will reply with an xdg_surface.configure event. The client must
acknowledge it and is then allowed to attach a buffer to map the surface.
Mapping an xdg_surface-based role surface is defined as making it
possible for the surface to be shown by the compositor. Note that
a mapped surface is not guaranteed to be visible once it is mapped.
For an xdg_surface to be mapped by the compositor, the following
conditions must be met:
(1) the client has assigned an xdg_surface-based role to the surface
(2) the client has set and committed the xdg_surface state and the
role-dependent state to the surface
(3) the client has committed a buffer to the surface
A newly-unmapped surface is considered to have met condition (1) out
of the 3 required conditions for mapping a surface if its role surface
has not been destroyed.
</description>
<enum name="error">
@ -416,20 +468,23 @@
See the documentation of xdg_toplevel for more details about what an
xdg_toplevel is and how it is used.
</description>
<arg name="id" type="new_id" interface="zxdg_toplevel_v6"/>
<arg name="id" type="new_id" interface="xdg_toplevel"/>
</request>
<request name="get_popup">
<description summary="assign the xdg_popup surface role">
This creates an xdg_popup object for the given xdg_surface and gives the
associated wl_surface the xdg_popup role.
This creates an xdg_popup object for the given xdg_surface and gives
the associated wl_surface the xdg_popup role.
If null is passed as a parent, a parent surface must be specified using
some other protocol, before committing the initial state.
See the documentation of xdg_popup for more details about what an
xdg_popup is and how it is used.
</description>
<arg name="id" type="new_id" interface="zxdg_popup_v6"/>
<arg name="parent" type="object" interface="zxdg_surface_v6"/>
<arg name="positioner" type="object" interface="zxdg_positioner_v6"/>
<arg name="id" type="new_id" interface="xdg_popup"/>
<arg name="parent" type="object" interface="xdg_surface" allow-null="true"/>
<arg name="positioner" type="object" interface="xdg_positioner"/>
</request>
<request name="set_window_geometry">
@ -442,6 +497,11 @@
The window geometry is double buffered, and will be applied at the
time wl_surface.commit of the corresponding wl_surface is called.
When maintaining a position, the compositor should treat the (x, y)
coordinate of the window geometry as the top left corner of the window.
A client changing the (x, y) window geometry coordinate should in
general not alter the position of the window.
Once the window geometry of the surface is set, it is not possible to
unset it, and it will remain the same until set_window_geometry is
called again, even if a new subsurface or buffer is attached.
@ -511,36 +571,57 @@
</description>
<arg name="serial" type="uint" summary="serial of the configure event"/>
</event>
</interface>
<interface name="zxdg_toplevel_v6" version="1">
<interface name="xdg_toplevel" version="3">
<description summary="toplevel surface">
This interface defines an xdg_surface role which allows a surface to,
among other things, set window-like properties such as maximize,
fullscreen, and minimize, set application-specific metadata like title and
id, and well as trigger user interactive operations such as interactive
resize and move.
Unmapping an xdg_toplevel means that the surface cannot be shown
by the compositor until it is explicitly mapped again.
All active operations (e.g., move, resize) are canceled and all
attributes (e.g. title, state, stacking, ...) are discarded for
an xdg_toplevel surface when it is unmapped. The xdg_toplevel returns to
the state it had right after xdg_surface.get_toplevel. The client
can re-map the toplevel by perfoming a commit without any buffer
attached, waiting for a configure event and handling it as usual (see
xdg_surface description).
Attaching a null buffer to a toplevel unmaps the surface.
</description>
<request name="destroy" type="destructor">
<description summary="destroy the xdg_toplevel">
Unmap and destroy the window. The window will be effectively
hidden from the user's point of view, and all state like
maximization, fullscreen, and so on, will be lost.
This request destroys the role surface and unmaps the surface;
see "Unmapping" behavior in interface section for details.
</description>
</request>
<request name="set_parent">
<description summary="set the parent of this surface">
Set the "parent" of this surface. This window should be stacked
above a parent. The parent surface must be mapped as long as this
surface is mapped.
Set the "parent" of this surface. This surface should be stacked
above the parent surface and all other ancestor surfaces.
Parent windows should be set on dialogs, toolboxes, or other
"auxiliary" surfaces, so that the parent is raised when the dialog
is raised.
Setting a null parent for a child window removes any parent-child
relationship for the child. Setting a null parent for a window which
currently has no parent is a no-op.
If the parent is unmapped then its children are managed as
though the parent of the now-unmapped parent has become the
parent of this surface. If no parent exists for the now-unmapped
parent then the children are managed as though they have no
parent surface.
</description>
<arg name="parent" type="object" interface="zxdg_toplevel_v6" allow-null="true"/>
<arg name="parent" type="object" interface="xdg_toplevel" allow-null="true"/>
</request>
<request name="set_title">
@ -573,6 +654,9 @@
For example, "org.freedesktop.FooViewer" where the .desktop file is
"org.freedesktop.FooViewer.desktop".
Like other properties, a set_app_id request can be sent after the
xdg_toplevel has been mapped to update the property.
See the desktop-entry specification [0] for more details on
application identifiers and how they relate to well-known D-Bus
names and .desktop files.
@ -676,7 +760,7 @@
</description>
<arg name="seat" type="object" interface="wl_seat" summary="the wl_seat of the user event"/>
<arg name="serial" type="uint" summary="the serial of the user event"/>
<arg name="edges" type="uint" summary="which edge or corner is being dragged"/>
<arg name="edges" type="uint" enum="resize_edge" summary="which edge or corner is being dragged"/>
</request>
<enum name="state">
@ -693,12 +777,18 @@
<description summary="the surface is maximized">
The surface is maximized. The window geometry specified in the configure
event must be obeyed by the client.
The client should draw without shadow or other
decoration outside of the window geometry.
</description>
</entry>
<entry name="fullscreen" value="2" summary="the surface is fullscreen">
<description summary="the surface is fullscreen">
The surface is fullscreen. The window geometry specified in the configure
event must be obeyed by the client.
The surface is fullscreen. The window geometry specified in the
configure event is a maximum; the client cannot resize beyond it. For
a surface to cover the whole fullscreened area, the geometry
dimensions must be obeyed by the client. For more details, see
xdg_toplevel.set_fullscreen.
</description>
</entry>
<entry name="resizing" value="3" summary="the surface is being resized">
@ -716,6 +806,30 @@
keyboard or pointer focus.
</description>
</entry>
<entry name="tiled_left" value="5" since="2">
<description summary="the surface is tiled">
The window is currently in a tiled layout and the left edge is
considered to be adjacent to another part of the tiling grid.
</description>
</entry>
<entry name="tiled_right" value="6" since="2">
<description summary="the surface is tiled">
The window is currently in a tiled layout and the right edge is
considered to be adjacent to another part of the tiling grid.
</description>
</entry>
<entry name="tiled_top" value="7" since="2">
<description summary="the surface is tiled">
The window is currently in a tiled layout and the top edge is
considered to be adjacent to another part of the tiling grid.
</description>
</entry>
<entry name="tiled_bottom" value="8" since="2">
<description summary="the surface is tiled">
The window is currently in a tiled layout and the bottom edge is
considered to be adjacent to another part of the tiling grid.
</description>
</entry>
</enum>
<request name="set_max_size">
@ -805,12 +919,11 @@
Maximize the surface.
After requesting that the surface should be maximized, the compositor
will respond by emitting a configure event with the "maximized" state
and the required window geometry. The client should then update its
content, drawing it in a maximized state, i.e. without shadow or other
decoration outside of the window geometry. The client must also
acknowledge the configure when committing the new content (see
ack_configure).
will respond by emitting a configure event. Whether this configure
actually sets the window maximized is subject to compositor policies.
The client must then update its content, drawing in the configured
state. The client must also acknowledge the configure when committing
the new content (see ack_configure).
It is up to the compositor to decide how and where to maximize the
surface, for example which output and what region of the screen should
@ -818,6 +931,10 @@
If the surface was already maximized, the compositor will still emit
a configure event with the "maximized" state.
If the surface is in a fullscreen state, this request has no direct
effect. It may alter the state the surface is returned to when
unmaximized unless overridden by the compositor.
</description>
</request>
@ -826,13 +943,13 @@
Unmaximize the surface.
After requesting that the surface should be unmaximized, the compositor
will respond by emitting a configure event without the "maximized"
state. If available, the compositor will include the window geometry
dimensions the window had prior to being maximized in the configure
request. The client must then update its content, drawing it in a
regular state, i.e. potentially with shadow, etc. The client must also
acknowledge the configure when committing the new content (see
ack_configure).
will respond by emitting a configure event. Whether this actually
un-maximizes the window is subject to compositor policies.
If available and applicable, the compositor will include the window
geometry dimensions the window had prior to being maximized in the
configure event. The client must then update its content, drawing it in
the configured state. The client must also acknowledge the configure
when committing the new content (see ack_configure).
It is up to the compositor to position the surface after it was
unmaximized; usually the position the surface had before maximizing, if
@ -840,24 +957,63 @@
If the surface was already not maximized, the compositor will still
emit a configure event without the "maximized" state.
If the surface is in a fullscreen state, this request has no direct
effect. It may alter the state the surface is returned to when
unmaximized unless overridden by the compositor.
</description>
</request>
<request name="set_fullscreen">
<description summary="set the window as fullscreen on a monitor">
<description summary="set the window as fullscreen on an output">
Make the surface fullscreen.
You can specify an output that you would prefer to be fullscreen.
If this value is NULL, it's up to the compositor to choose which
display will be used to map this surface.
After requesting that the surface should be fullscreened, the
compositor will respond by emitting a configure event. Whether the
client is actually put into a fullscreen state is subject to compositor
policies. The client must also acknowledge the configure when
committing the new content (see ack_configure).
The output passed by the request indicates the client's preference as
to which display it should be set fullscreen on. If this value is NULL,
it's up to the compositor to choose which display will be used to map
this surface.
If the surface doesn't cover the whole output, the compositor will
position the surface in the center of the output and compensate with
black borders filling the rest of the output.
with border fill covering the rest of the output. The content of the
border fill is undefined, but should be assumed to be in some way that
attempts to blend into the surrounding area (e.g. solid black).
If the fullscreened surface is not opaque, the compositor must make
sure that other screen content not part of the same surface tree (made
up of subsurfaces, popups or similarly coupled surfaces) are not
visible below the fullscreened surface.
</description>
<arg name="output" type="object" interface="wl_output" allow-null="true"/>
</request>
<request name="unset_fullscreen" />
<request name="unset_fullscreen">
<description summary="unset the window as fullscreen">
Make the surface no longer fullscreen.
After requesting that the surface should be unfullscreened, the
compositor will respond by emitting a configure event.
Whether this actually removes the fullscreen state of the client is
subject to compositor policies.
Making a surface unfullscreen sets states for the surface based on the following:
* the state(s) it may have had before becoming fullscreen
* any state(s) decided by the compositor
* any state(s) requested by the client while the surface was fullscreen
The compositor may include the previous window geometry dimensions in
the configure event, if applicable.
The client must also acknowledge the configure when committing the new
content (see ack_configure).
</description>
</request>
<request name="set_minimized">
<description summary="set the window as minimized">
@ -913,7 +1069,7 @@
</event>
</interface>
<interface name="zxdg_popup_v6" version="1">
<interface name="xdg_popup" version="3">
<description summary="short-lived, popup surfaces for menus">
A popup surface is a short-lived, temporary surface. It can be used to
implement for example menus, popovers, tooltips and other similar user
@ -931,21 +1087,12 @@
surface of their own is clicked should dismiss the popup using the destroy
request.
The parent surface must have either the xdg_toplevel or xdg_popup surface
role.
A newly created xdg_popup will be stacked on top of all previously created
xdg_popup surfaces associated with the same xdg_toplevel.
The parent of an xdg_popup must be mapped (see the xdg_surface
description) before the xdg_popup itself.
The x and y arguments passed when creating the popup object specify
where the top left of the popup should be placed, relative to the
local surface coordinates of the parent surface. See
xdg_surface.get_popup. An xdg_popup must intersect with or be at least
partially adjacent to its parent surface.
The client must call wl_surface.commit on the corresponding wl_surface
for the xdg_popup state to take effect.
</description>
@ -1023,6 +1170,11 @@
The x and y arguments represent the position the popup was placed at
given the xdg_positioner rule, relative to the upper left corner of the
window geometry of the parent surface.
For version 2 or older, the configure event for an xdg_popup is only
ever sent once for the initial configuration. Starting with version 3,
it may be sent again if the popup is setup with an xdg_positioner with
set_reactive requested, or in response to xdg_popup.reposition requests.
</description>
<arg name="x" type="int"
summary="x position relative to parent surface window geometry"/>
@ -1040,5 +1192,58 @@
</description>
</event>
<!-- Version 3 additions -->
<request name="reposition" since="3">
<description summary="recalculate the popup's location">
Reposition an already-mapped popup. The popup will be placed given the
details in the passed xdg_positioner object, and a
xdg_popup.repositioned followed by xdg_popup.configure and
xdg_surface.configure will be emitted in response. Any parameters set
by the previous positioner will be discarded.
The passed token will be sent in the corresponding
xdg_popup.repositioned event. The new popup position will not take
effect until the corresponding configure event is acknowledged by the
client. See xdg_popup.repositioned for details. The token itself is
opaque, and has no other special meaning.
If multiple reposition requests are sent, the compositor may skip all
but the last one.
If the popup is repositioned in response to a configure event for its
parent, the client should send an xdg_positioner.set_parent_configure
and possibly a xdg_positioner.set_parent_size request to allow the
compositor to properly constrain the popup.
If the popup is repositioned together with a parent that is being
resized, but not in response to a configure event, the client should
send a xdg_positioner.set_parent_size request.
</description>
<arg name="positioner" type="object" interface="xdg_positioner"/>
<arg name="token" type="uint" summary="reposition request token"/>
</request>
<event name="repositioned" since="3">
<description summary="signal the completion of a repositioned request">
The repositioned event is sent as part of a popup configuration
sequence, together with xdg_popup.configure and lastly
xdg_surface.configure to notify the completion of a reposition request.
The repositioned event is to notify about the completion of a
xdg_popup.reposition request. The token argument is the token passed
in the xdg_popup.reposition request.
Immediately after this event is emitted, xdg_popup.configure and
xdg_surface.configure will be sent with the updated size and position,
as well as a new configure serial.
The client should optionally update the content of the popup, but must
acknowledge the new popup configuration for the new position to take
effect. See xdg_surface.ack_configure for details.
</description>
<arg name="token" type="uint" summary="reposition request token"/>
</event>
</interface>
</protocol>

View File

@ -0,0 +1,244 @@
# Copyright 2021 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Utility class for analyzing sommelier buffer statistics."""
import argparse
from collections import defaultdict
import sys
class Action(object):
"""Hold data about sommelier buffer operations."""
__slots__ = ("type", "bid", "time")
def __init__(self, action_type, bid, time):
self.type = action_type
self.bid = bid
self.time = time
class Stats:
"""For processing text files of sommelier data and printing their stats."""
def __init__(self):
self.surfaces = defaultdict(list)
self.bid_sid = {}
self.sid_bid = {}
def read(self, filename):
"""Read in a sommelier-timing output file for later processing.
Sample file:
Event, Type, Surface_ID, Buffer_ID, Time # header line 1
0 surface_attach 12 23 1612345678.987654321 # line 2
1 surface_commit 12 -1 1612345678.987654325 # lines 3, 4, ...
Run sommelier with the timing-filename option to generate an event log:
sommelier -X --glamor --trace-system \
--timing-filename=out.txt glxgears &
Args:
filename (string): The path to the buffer time data.
"""
with open(filename, "r") as f:
# Skip the header line.
f.readline()
for line in f:
try:
[_, action_type, sid, bid, timestamp] = line.split()
except ValueError:
continue
sid = int(sid)
bid = int(bid)
# use dictionary history to fill missing ids
if sid != -1 and bid != -1:
self.sid_bid[sid] = bid
self.bid_sid[bid] = sid
elif sid in self.sid_bid:
bid = self.sid_bid[sid]
elif bid in self.bid_sid:
sid = self.bid_sid[bid]
# convert string time into floating point time
time = float(timestamp)
self.surfaces[sid].append(Action(action_type, bid, time))
def add(self, durr, tot, err):
"""Kahan sum function to reduce floating point error.
Args:
durr (float): The elapsed time between two actions.
tot (float): The summed total of elapsed times.
err (float): The accumulated error.
Returns:
tot (int): the new summed total of elapsed times.
err (int): the new accumulated error.
"""
y = durr - err
t = tot + y
err = (t - tot) - y
tot = t
return tot, err
def add_acr(self, sid, time, acr, idx):
"""Update the sum/total, error, and count of each action type.
Args:
sid (int): the id of the surface to display stats for.
time (float): the time the current action to place.
acr (dict): A dicitionary containing numerical info on accumulated stats.
idx (dict): A dictionary containing the last index of an action
"""
for action, pos in idx.items():
if pos is not None:
prev_time = self.surfaces[sid][pos].time
x = action[0] # use first letter of action type in key names
tot, err = self.add(time - prev_time, acr[x], acr[x + "err"])
acr[x] = tot
acr[x + "err"] = err
acr[x + "_count"] += 1
def print_stats(self, sid):
"""Print the average times between calls to a surface.
Args:
sid (int): the id of the surface to display stats for.
"""
# to_attach - a dict used in calculating summed difference from any other
# call (a, c, r) to an "attach" call.
# a - attach, c - commit, r - release
# aerr - accumulated numerical error from summing differences.
# a_count - count of attach-attach differences so far.
to_attach = {
"a": 0,
"aerr": 0,
"a_count": 0,
"c": 0,
"cerr": 0,
"c_count": 0,
"r": 0,
"rerr": 0,
"r_count": 0,
}
to_commit = {
"a": 0,
"aerr": 0,
"a_count": 0,
"c": 0,
"cerr": 0,
"c_count": 0,
"r": 0,
"rerr": 0,
"r_count": 0,
}
to_release = {
"a": 0,
"aerr": 0,
"a_count": 0,
"c": 0,
"cerr": 0,
"c_count": 0,
"r": 0,
"rerr": 0,
"r_count": 0,
}
# idx stores the last index of an attach, commit, and release call.
idx = {"attach": None, "commit": None, "release": None}
# For every action, calculate summed difference based on its type.
# e.g. for a commit action, calculate time between it and the previous
# attach, commit, and release call using to_commit.
for i, action in enumerate(self.surfaces[sid]):
time = action.time
if action.type == "attach":
self.add_acr(sid, time, to_attach, idx)
idx["attach"] = i
elif action.type == "commit":
self.add_acr(sid, time, to_commit, idx)
idx["commit"] = i
elif action.type == "release":
self.add_acr(sid, time, to_release, idx)
idx["release"] = i
print(
"attach-attach avg: ",
to_attach["a"] * 1000 / to_attach["a_count"],
" ms",
)
print(
"commit-attach avg: ",
to_attach["c"] * 1000 / to_attach["c_count"],
" ms",
)
print(
"release-attach avg: ",
to_attach["r"] * 1000 / to_attach["r_count"],
" ms",
)
print()
print(
"commit-commit avg: ",
to_commit["c"] * 1000 / to_commit["c_count"],
" ms",
)
print(
"attach-commit avg: ",
to_commit["a"] * 1000 / to_commit["a_count"],
" ms",
)
print(
"release-commit avg: ",
to_commit["r"] * 1000 / to_commit["r_count"],
" ms",
)
print()
print(
"release-release avg: ",
to_release["r"] * 1000 / to_release["r_count"],
" ms",
)
print(
"attach-release avg: ",
to_release["a"] * 1000 / to_release["a_count"],
" ms",
)
print(
"commit-release avg: ",
to_release["c"] * 1000 / to_release["c_count"],
" ms",
)
def main(args):
parser = argparse.ArgumentParser(
description="Compute average of interprocess times"
)
parser.add_argument("file", type=str, help="the timing log file to process")
parser.add_argument(
"--surface_id",
type=int,
help="the id of the surface to compute averages on",
)
args = parser.parse_args()
log = args.file
sid = args.surface_id
stats = Stats()
stats.read(log)
# Print buffer stats for the sid with the most number of actions.
if not sid:
sid = next(iter(stats.surfaces.keys()))
for key in stats.surfaces:
if len(stats.surfaces[key]) > len(stats.surfaces[sid]):
sid = key
stats.print_stats(sid)
if __name__ == "__main__":
main(sys.argv[1:])

View File

@ -0,0 +1,279 @@
#!/usr/bin/env python3
# Copyright 2022 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Summarizes Sommelier timing information."""
import argparse
from enum import Enum
import statistics
from typing import NamedTuple
_MS_PER_SEC = 1000
_US_PER_SEC = 1000000
# Floating point error bounds
_FP_ERROR = 0.01
class EventType(Enum):
"""Wayland event type."""
COMMIT = 1
ATTACH = 2
RELEASE = 3
UNKNOWN = 4
class EventInfo(NamedTuple):
"""Stores information of an event."""
event_type: EventType
surface_id: int
buffer_id: int
time: float
def parse_event_type(event_type):
EVENT_MAP = {
"a": EventType.ATTACH,
"c": EventType.COMMIT,
"r": EventType.RELEASE,
}
return EVENT_MAP.get(event_type, EventType.UNKNOWN)
class FrameLog:
"""Manages access to the Sommelier timing logs."""
def __init__(self, filename):
"""Parse Sommelier timing log.
Format of log (header line might be truncated):
Type Surface_ID Buffer_ID Delta_Time # header line 1
a 12 20 4237.44 # line 2
....
EndTime 3972 1655330324.7 # last line
Last line format: (EndTime, last event id, time since epoch (s))
"""
self.frame_log = []
self.surfaces = set()
with open(filename, "r") as f:
lines = f.read().splitlines()
total_delta_time = 0
last_line = lines[-1].split(" ")
if len(last_line) != 3 or last_line[0] != "EndTime":
print(f"Invalid EndTime: {lines[-1]}")
return
self.end_time = float(last_line[2])
for l in reversed(lines[1:-1]):
line = l.rstrip().split(" ")
# Skip parsing line that is improperly formatted
if len(line) != 4:
continue
total_delta_time += float(line[3]) / _US_PER_SEC
surface_id = int(line[1])
info = EventInfo(
event_type=parse_event_type(line[0]),
surface_id=surface_id,
buffer_id=int(line[2]),
time=self.end_time - total_delta_time,
)
self.frame_log.append(info)
self.surfaces.add(surface_id)
def get_target_ft(self, target_fps, avg_fps):
"""Outputs target frame time given a target fps.
If target_fps is None, determine automatically based on
average FPS.
Args:
target_fps: Target FPS (30, 60, None).
avg_fps: Average FPS over time window.
"""
if not target_fps:
# determines appropriate target FPS by finding whether the
# average is closer to 30 or 60.
# TODO(asriniva): Revisit this methodology. What should a title
# averaging 31-33 FPS target? Non 30/60 FPS targets?
target_fps = min([30, 60], key=lambda x: abs(x - avg_fps))
# Acceptable frame time ranges, based on Battlestar's metrics.
# The +/-3 bounds do not scale with FPS (small variance for high FPS,
# large variance for low FPS) but can account for variability in
# hardware.
return [_MS_PER_SEC / target_fps - 3, _MS_PER_SEC / target_fps + 3]
def output_fps(self, surface, windows, max_ft_ms, target_fps):
"""Outputs the summarized fps information based on frame log.
Args:
surface: Surface ID
windows: List of time windows (in seconds) to summarize.
max_ft_ms: Max frame time threshold (ms).
target_fps: Target FPS, either 30, 60, or
None (automatically determined).
"""
max_frame_ms = 0
# only check for commit events on the given surface
# events are in reverse chronological order
events = [
e
for e in self.frame_log
if e.surface_id == surface and e.event_type == EventType.COMMIT
]
if not events:
print(f"No commit events found for surface {surface}\n")
return
total_sec = self.end_time - events[-1].time
ft_target_ms = self.get_target_ft(target_fps, len(events) / total_sec)
for w_sec in windows + [total_sec]:
# num frames in acceptable range
target_frames = 0
# num frames exceeding max_ft_ms
max_ft_events = 0
prev_sec = self.end_time
frame_count = 0
frames_ms = []
for event in events:
frame_ms = (prev_sec - event.time) * _MS_PER_SEC
frames_ms.append(frame_ms)
max_frame_ms = max(max_frame_ms, frame_ms)
if ft_target_ms[0] < frame_ms < ft_target_ms[1]:
target_frames += 1
if frame_ms > max_ft_ms:
max_ft_events += 1
current_sec = self.end_time - event.time
frame_count += 1
# handles floating point error in the case when
# w_sec == total_sec
if current_sec > w_sec - _FP_ERROR:
print(f"Summary for last {w_sec} seconds")
print("-------------------------------")
print(f"FPS: {frame_count / current_sec}")
print(f"Max frame time: {max_frame_ms} ms")
print(f"Frame count: {frame_count} frames")
print(
f"Percentage frames within acceptable target "
f"{ft_target_ms} ms: "
f"{target_frames * 100/frame_count}%"
)
if len(frames_ms) > 1:
c_var = statistics.stdev(frames_ms) / statistics.mean(
frames_ms
)
print(f"Coefficient of variation: {c_var}")
print(
f"Frames exceeding max frame time threshold"
f" {max_ft_ms} ms:"
f" {max_ft_events} frames"
)
print()
break
prev_sec = event.time
print()
def output_fps_sliding(self, surface, window, max_ft_ms):
"""Outputs the summarized fps information based on frame log.
Args:
surface: Surface ID
window: Window size (in num of frames).
max_ft_ms: Max frame time threshold (ms).
target_fps: Target FPS, either 30, 60, or
None (automatically determined).
"""
print(f"Sliding window aggregates for {window} events")
print("-------------------------------")
# only check for commit events on the given surface
# events are in reverse chronological order
events = [
e
for e in self.frame_log
if e.surface_id == surface and e.event_type == EventType.COMMIT
]
if not events:
print(f"No commit events found for surface {surface}\n")
return
# For a sliding window, aggregate the following:
# cvars: Coefficient of variation over window
# max_ft_events: Number of frames over max_ft_ms
cvars = []
max_ft_events = []
for i in range(window, len(events)):
sl_window = events[i - window : i]
frames_ms = [
(sl_window[x].time - sl_window[x + 1].time) * _MS_PER_SEC
for x in range(window - 1)
]
max_fts = sum([1 for x in frames_ms if x > max_ft_ms])
c_var = statistics.stdev(frames_ms) / statistics.mean(frames_ms)
cvars.append(c_var)
max_ft_events.append(max_fts)
if len(cvars) > 1:
print(
"Arithmetic mean of coefficient of variation:",
statistics.fmean(cvars),
)
print(
"Geometric mean of coefficient of variation:",
statistics.geometric_mean(cvars),
)
if len(max_ft_events) > 1:
print(
f"Average number of frame time events over threshold"
f" {max_ft_ms} ms:",
statistics.mean(max_ft_events),
)
print()
print()
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Return frame summary based on Sommelier timing log."
)
parser.add_argument("file", help="Filename of timing log")
parser.add_argument(
"--windows",
action="extend",
type=int,
nargs="+",
help="Time windows for summary (in seconds)",
default=[10, 60, 300],
)
parser.add_argument(
"--target-fps", type=int, help="Target FPS", default=None
)
parser.add_argument(
"--max-frame-time",
type=float,
help="Max frame time threshold (in milliseconds)",
default=200,
)
parser.add_argument(
"--sliding",
type=int,
help="Use sliding window with size in number of frames",
default=300,
)
args = parser.parse_args()
if args.target_fps and args.target_fps < 20:
parser.error("Choose target FPS above 20 FPS")
log = FrameLog(args.file)
for s in log.surfaces:
print(f"Summary for surface {s}")
print("-------------------------------")
log.output_fps_sliding(
s, max_ft_ms=args.max_frame_time, window=args.sliding
)
log.output_fps(
s,
windows=sorted(args.windows),
max_ft_ms=args.max_frame_time,
target_fps=args.target_fps,
)

View File

@ -1,888 +0,0 @@
// Copyright 2018 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "sommelier.h"
#include <assert.h>
#include <errno.h>
#include <gbm.h>
#include <limits.h>
#include <linux/virtwl.h>
#include <pixman.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <wayland-client.h>
#include <wayland-util.h>
#include "drm-server-protocol.h"
#include "linux-dmabuf-unstable-v1-client-protocol.h"
#include "viewporter-client-protocol.h"
#define MIN_SIZE (INT_MIN / 10)
#define MAX_SIZE (INT_MAX / 10)
#define DMA_BUF_SYNC_READ (1 << 0)
#define DMA_BUF_SYNC_WRITE (2 << 0)
#define DMA_BUF_SYNC_RW (DMA_BUF_SYNC_READ | DMA_BUF_SYNC_WRITE)
#define DMA_BUF_SYNC_START (0 << 2)
#define DMA_BUF_SYNC_END (1 << 2)
#define DMA_BUF_BASE 'b'
#define DMA_BUF_IOCTL_SYNC _IOW(DMA_BUF_BASE, 0, struct dma_buf_sync)
struct sl_host_compositor {
struct sl_compositor* compositor;
struct wl_resource* resource;
struct wl_compositor* proxy;
};
struct sl_output_buffer {
struct wl_list link;
uint32_t width;
uint32_t height;
uint32_t format;
struct wl_buffer* internal;
struct sl_mmap* mmap;
struct pixman_region32 damage;
struct sl_host_surface* surface;
};
struct dma_buf_sync {
__u64 flags;
};
static void sl_dmabuf_sync(int fd, __u64 flags) {
struct dma_buf_sync sync = {0};
int rv;
sync.flags = flags;
do {
rv = ioctl(fd, DMA_BUF_IOCTL_SYNC, &sync);
} while (rv == -1 && errno == EINTR);
}
static void sl_dmabuf_begin_write(int fd) {
sl_dmabuf_sync(fd, DMA_BUF_SYNC_START | DMA_BUF_SYNC_WRITE);
}
static void sl_dmabuf_end_write(int fd) {
sl_dmabuf_sync(fd, DMA_BUF_SYNC_END | DMA_BUF_SYNC_WRITE);
}
static void sl_virtwl_dmabuf_sync(int fd, __u32 flags) {
struct virtwl_ioctl_dmabuf_sync sync = {0};
int rv;
sync.flags = flags;
rv = ioctl(fd, VIRTWL_IOCTL_DMABUF_SYNC, &sync);
assert(!rv);
UNUSED(rv);
}
static void sl_virtwl_dmabuf_begin_write(int fd) {
sl_virtwl_dmabuf_sync(fd, DMA_BUF_SYNC_START | DMA_BUF_SYNC_WRITE);
}
static void sl_virtwl_dmabuf_end_write(int fd) {
sl_virtwl_dmabuf_sync(fd, DMA_BUF_SYNC_END | DMA_BUF_SYNC_WRITE);
}
static uint32_t sl_gbm_format_for_shm_format(uint32_t format) {
switch (format) {
case WL_SHM_FORMAT_NV12:
return GBM_FORMAT_NV12;
case WL_SHM_FORMAT_RGB565:
return GBM_FORMAT_RGB565;
case WL_SHM_FORMAT_ARGB8888:
return GBM_FORMAT_ARGB8888;
case WL_SHM_FORMAT_ABGR8888:
return GBM_FORMAT_ABGR8888;
case WL_SHM_FORMAT_XRGB8888:
return GBM_FORMAT_XRGB8888;
case WL_SHM_FORMAT_XBGR8888:
return GBM_FORMAT_XBGR8888;
}
assert(0);
return 0;
}
static uint32_t sl_drm_format_for_shm_format(int format) {
switch (format) {
case WL_SHM_FORMAT_NV12:
return WL_DRM_FORMAT_NV12;
case WL_SHM_FORMAT_RGB565:
return WL_DRM_FORMAT_RGB565;
case WL_SHM_FORMAT_ARGB8888:
return WL_DRM_FORMAT_ARGB8888;
case WL_SHM_FORMAT_ABGR8888:
return WL_DRM_FORMAT_ABGR8888;
case WL_SHM_FORMAT_XRGB8888:
return WL_DRM_FORMAT_XRGB8888;
case WL_SHM_FORMAT_XBGR8888:
return WL_DRM_FORMAT_XBGR8888;
}
assert(0);
return 0;
}
static void sl_output_buffer_destroy(struct sl_output_buffer* buffer) {
wl_buffer_destroy(buffer->internal);
sl_mmap_unref(buffer->mmap);
pixman_region32_fini(&buffer->damage);
wl_list_remove(&buffer->link);
free(buffer);
}
static void sl_output_buffer_release(void* data, struct wl_buffer* buffer) {
struct sl_output_buffer* output_buffer = wl_buffer_get_user_data(buffer);
struct sl_host_surface* host_surface = output_buffer->surface;
wl_list_remove(&output_buffer->link);
wl_list_insert(&host_surface->released_buffers, &output_buffer->link);
}
static const struct wl_buffer_listener sl_output_buffer_listener = {
sl_output_buffer_release};
static void sl_host_surface_destroy(struct wl_client* client,
struct wl_resource* resource) {
wl_resource_destroy(resource);
}
static void sl_host_surface_attach(struct wl_client* client,
struct wl_resource* resource,
struct wl_resource* buffer_resource,
int32_t x,
int32_t y) {
struct sl_host_surface* host = wl_resource_get_user_data(resource);
struct sl_host_buffer* host_buffer =
buffer_resource ? wl_resource_get_user_data(buffer_resource) : NULL;
struct wl_buffer* buffer_proxy = NULL;
struct sl_window* window;
double scale = host->ctx->scale;
host->current_buffer = NULL;
if (host->contents_shm_mmap) {
sl_mmap_unref(host->contents_shm_mmap);
host->contents_shm_mmap = NULL;
}
if (host_buffer) {
host->contents_width = host_buffer->width;
host->contents_height = host_buffer->height;
buffer_proxy = host_buffer->proxy;
if (host_buffer->shm_mmap)
host->contents_shm_mmap = sl_mmap_ref(host_buffer->shm_mmap);
}
if (host->contents_shm_mmap) {
while (!wl_list_empty(&host->released_buffers)) {
host->current_buffer = wl_container_of(host->released_buffers.next,
host->current_buffer, link);
if (host->current_buffer->width == host_buffer->width &&
host->current_buffer->height == host_buffer->height &&
host->current_buffer->format == host_buffer->shm_format) {
break;
}
sl_output_buffer_destroy(host->current_buffer);
host->current_buffer = NULL;
}
// Allocate new output buffer.
if (!host->current_buffer) {
size_t width = host_buffer->width;
size_t height = host_buffer->height;
uint32_t shm_format = host_buffer->shm_format;
size_t bpp = sl_shm_bpp_for_shm_format(shm_format);
size_t num_planes = sl_shm_num_planes_for_shm_format(shm_format);
host->current_buffer = malloc(sizeof(struct sl_output_buffer));
assert(host->current_buffer);
wl_list_insert(&host->released_buffers, &host->current_buffer->link);
host->current_buffer->width = width;
host->current_buffer->height = height;
host->current_buffer->format = shm_format;
host->current_buffer->surface = host;
pixman_region32_init_rect(&host->current_buffer->damage, 0, 0, MAX_SIZE,
MAX_SIZE);
switch (host->ctx->shm_driver) {
case SHM_DRIVER_DMABUF: {
struct zwp_linux_buffer_params_v1* buffer_params;
struct gbm_bo* bo;
int stride0;
int fd;
bo = gbm_bo_create(host->ctx->gbm, width, height,
sl_gbm_format_for_shm_format(shm_format),
GBM_BO_USE_SCANOUT | GBM_BO_USE_LINEAR);
stride0 = gbm_bo_get_stride(bo);
fd = gbm_bo_get_fd(bo);
buffer_params = zwp_linux_dmabuf_v1_create_params(
host->ctx->linux_dmabuf->internal);
zwp_linux_buffer_params_v1_add(buffer_params, fd, 0, 0, stride0, 0,
0);
host->current_buffer->internal =
zwp_linux_buffer_params_v1_create_immed(
buffer_params, width, height,
sl_drm_format_for_shm_format(shm_format), 0);
zwp_linux_buffer_params_v1_destroy(buffer_params);
host->current_buffer->mmap = sl_mmap_create(
fd, height * stride0, bpp, 1, 0, stride0, 0, 0, 1, 0);
host->current_buffer->mmap->begin_write = sl_dmabuf_begin_write;
host->current_buffer->mmap->end_write = sl_dmabuf_end_write;
gbm_bo_destroy(bo);
} break;
case SHM_DRIVER_VIRTWL: {
size_t size = host_buffer->shm_mmap->size;
struct virtwl_ioctl_new ioctl_new = {.type = VIRTWL_IOCTL_NEW_ALLOC,
.fd = -1,
.flags = 0,
.size = size};
struct wl_shm_pool* pool;
int rv;
rv = ioctl(host->ctx->virtwl_fd, VIRTWL_IOCTL_NEW, &ioctl_new);
assert(rv == 0);
UNUSED(rv);
pool =
wl_shm_create_pool(host->ctx->shm->internal, ioctl_new.fd, size);
host->current_buffer->internal = wl_shm_pool_create_buffer(
pool, 0, width, height, host_buffer->shm_mmap->stride[0],
shm_format);
wl_shm_pool_destroy(pool);
host->current_buffer->mmap = sl_mmap_create(
ioctl_new.fd, size, bpp, num_planes, 0,
host_buffer->shm_mmap->stride[0],
host_buffer->shm_mmap->offset[1] -
host_buffer->shm_mmap->offset[0],
host_buffer->shm_mmap->stride[1], host_buffer->shm_mmap->y_ss[0],
host_buffer->shm_mmap->y_ss[1]);
} break;
case SHM_DRIVER_VIRTWL_DMABUF: {
uint32_t drm_format = sl_drm_format_for_shm_format(shm_format);
struct virtwl_ioctl_new ioctl_new = {
.type = VIRTWL_IOCTL_NEW_DMABUF,
.fd = -1,
.flags = 0,
.dmabuf = {
.width = width, .height = height, .format = drm_format}};
struct zwp_linux_buffer_params_v1* buffer_params;
size_t size;
int rv;
rv = ioctl(host->ctx->virtwl_fd, VIRTWL_IOCTL_NEW, &ioctl_new);
if (rv) {
fprintf(stderr, "error: virtwl dmabuf allocation failed: %s\n",
strerror(errno));
_exit(EXIT_FAILURE);
}
size = ioctl_new.dmabuf.stride0 * height;
buffer_params = zwp_linux_dmabuf_v1_create_params(
host->ctx->linux_dmabuf->internal);
zwp_linux_buffer_params_v1_add(buffer_params, ioctl_new.fd, 0,
ioctl_new.dmabuf.offset0,
ioctl_new.dmabuf.stride0, 0, 0);
if (num_planes > 1) {
zwp_linux_buffer_params_v1_add(buffer_params, ioctl_new.fd, 1,
ioctl_new.dmabuf.offset1,
ioctl_new.dmabuf.stride1, 0, 0);
size = MAX(size, ioctl_new.dmabuf.offset1 +
ioctl_new.dmabuf.stride1 * height /
host_buffer->shm_mmap->y_ss[1]);
}
host->current_buffer->internal =
zwp_linux_buffer_params_v1_create_immed(buffer_params, width,
height, drm_format, 0);
zwp_linux_buffer_params_v1_destroy(buffer_params);
host->current_buffer->mmap = sl_mmap_create(
ioctl_new.fd, size, bpp, num_planes, ioctl_new.dmabuf.offset0,
ioctl_new.dmabuf.stride0, ioctl_new.dmabuf.offset1,
ioctl_new.dmabuf.stride1, host_buffer->shm_mmap->y_ss[0],
host_buffer->shm_mmap->y_ss[1]);
host->current_buffer->mmap->begin_write =
sl_virtwl_dmabuf_begin_write;
host->current_buffer->mmap->end_write = sl_virtwl_dmabuf_end_write;
} break;
}
assert(host->current_buffer->internal);
assert(host->current_buffer->mmap);
wl_buffer_set_user_data(host->current_buffer->internal,
host->current_buffer);
wl_buffer_add_listener(host->current_buffer->internal,
&sl_output_buffer_listener, host->current_buffer);
}
}
x /= scale;
y /= scale;
// TODO(davidriley): This should be done in the commit.
if (host_buffer && host_buffer->sync_point) {
host_buffer->sync_point->sync(host->ctx, host_buffer->sync_point);
}
if (host->current_buffer) {
assert(host->current_buffer->internal);
wl_surface_attach(host->proxy, host->current_buffer->internal, x, y);
} else {
wl_surface_attach(host->proxy, buffer_proxy, x, y);
}
wl_list_for_each(window, &host->ctx->windows, link) {
if (window->host_surface_id == wl_resource_get_id(resource)) {
while (sl_process_pending_configure_acks(window, host))
continue;
break;
}
}
}
static void sl_host_surface_damage(struct wl_client* client,
struct wl_resource* resource,
int32_t x,
int32_t y,
int32_t width,
int32_t height) {
struct sl_host_surface* host = wl_resource_get_user_data(resource);
double scale = host->ctx->scale;
struct sl_output_buffer* buffer;
int64_t x1, y1, x2, y2;
wl_list_for_each(buffer, &host->busy_buffers, link) {
pixman_region32_union_rect(&buffer->damage, &buffer->damage, x, y, width,
height);
}
wl_list_for_each(buffer, &host->released_buffers, link) {
pixman_region32_union_rect(&buffer->damage, &buffer->damage, x, y, width,
height);
}
x1 = x;
y1 = y;
x2 = x1 + width;
y2 = y1 + height;
// Enclosing rect after scaling and outset by one pixel to account for
// potential filtering.
x1 = MAX(MIN_SIZE, x1 - 1) / scale;
y1 = MAX(MIN_SIZE, y1 - 1) / scale;
x2 = ceil(MIN(x2 + 1, MAX_SIZE) / scale);
y2 = ceil(MIN(y2 + 1, MAX_SIZE) / scale);
wl_surface_damage(host->proxy, x1, y1, x2 - x1, y2 - y1);
}
static void sl_frame_callback_done(void* data,
struct wl_callback* callback,
uint32_t time) {
struct sl_host_callback* host = wl_callback_get_user_data(callback);
wl_callback_send_done(host->resource, time);
wl_resource_destroy(host->resource);
}
static const struct wl_callback_listener sl_frame_callback_listener = {
sl_frame_callback_done};
static void sl_host_callback_destroy(struct wl_resource* resource) {
struct sl_host_callback* host = wl_resource_get_user_data(resource);
wl_callback_destroy(host->proxy);
wl_resource_set_user_data(resource, NULL);
free(host);
}
static void sl_host_surface_frame(struct wl_client* client,
struct wl_resource* resource,
uint32_t callback) {
struct sl_host_surface* host = wl_resource_get_user_data(resource);
struct sl_host_callback* host_callback;
host_callback = malloc(sizeof(*host_callback));
assert(host_callback);
host_callback->resource =
wl_resource_create(client, &wl_callback_interface, 1, callback);
wl_resource_set_implementation(host_callback->resource, NULL, host_callback,
sl_host_callback_destroy);
host_callback->proxy = wl_surface_frame(host->proxy);
wl_callback_set_user_data(host_callback->proxy, host_callback);
wl_callback_add_listener(host_callback->proxy, &sl_frame_callback_listener,
host_callback);
}
static void sl_host_surface_set_opaque_region(
struct wl_client* client,
struct wl_resource* resource,
struct wl_resource* region_resource) {
struct sl_host_surface* host = wl_resource_get_user_data(resource);
struct sl_host_region* host_region =
region_resource ? wl_resource_get_user_data(region_resource) : NULL;
wl_surface_set_opaque_region(host->proxy,
host_region ? host_region->proxy : NULL);
}
static void sl_host_surface_set_input_region(
struct wl_client* client,
struct wl_resource* resource,
struct wl_resource* region_resource) {
struct sl_host_surface* host = wl_resource_get_user_data(resource);
struct sl_host_region* host_region =
region_resource ? wl_resource_get_user_data(region_resource) : NULL;
wl_surface_set_input_region(host->proxy,
host_region ? host_region->proxy : NULL);
}
static void sl_host_surface_commit(struct wl_client* client,
struct wl_resource* resource) {
struct sl_host_surface* host = wl_resource_get_user_data(resource);
struct sl_viewport* viewport = NULL;
struct sl_window* window;
if (!wl_list_empty(&host->contents_viewport))
viewport = wl_container_of(host->contents_viewport.next, viewport, link);
if (host->contents_shm_mmap) {
uint8_t* src_addr = host->contents_shm_mmap->addr;
uint8_t* dst_addr = host->current_buffer->mmap->addr;
size_t* src_offset = host->contents_shm_mmap->offset;
size_t* dst_offset = host->current_buffer->mmap->offset;
size_t* src_stride = host->contents_shm_mmap->stride;
size_t* dst_stride = host->current_buffer->mmap->stride;
size_t* y_ss = host->contents_shm_mmap->y_ss;
size_t bpp = host->contents_shm_mmap->bpp;
size_t num_planes = host->contents_shm_mmap->num_planes;
double contents_scale_x = host->contents_scale;
double contents_scale_y = host->contents_scale;
double contents_offset_x = 0.0;
double contents_offset_y = 0.0;
pixman_box32_t* rect;
int n;
// Determine scale and offset for damage based on current viewport.
if (viewport) {
double contents_width = host->contents_width;
double contents_height = host->contents_height;
if (viewport->src_x >= 0 && viewport->src_y >= 0) {
contents_offset_x = wl_fixed_to_double(viewport->src_x);
contents_offset_y = wl_fixed_to_double(viewport->src_y);
}
if (viewport->dst_width > 0 && viewport->dst_height > 0) {
contents_scale_x *= contents_width / viewport->dst_width;
contents_scale_y *= contents_height / viewport->dst_height;
// Take source rectangle into account when both destionation size and
// source rectangle are set. If only source rectangle is set, then
// it determines the surface size so it can be ignored.
if (viewport->src_width >= 0 && viewport->src_height >= 0) {
contents_scale_x *=
wl_fixed_to_double(viewport->src_width) / contents_width;
contents_scale_y *=
wl_fixed_to_double(viewport->src_height) / contents_height;
}
}
}
if (host->current_buffer->mmap->begin_write)
host->current_buffer->mmap->begin_write(host->current_buffer->mmap->fd);
rect = pixman_region32_rectangles(&host->current_buffer->damage, &n);
while (n--) {
int32_t x1, y1, x2, y2;
// Enclosing rect after applying scale and offset.
x1 = rect->x1 * contents_scale_x + contents_offset_x;
y1 = rect->y1 * contents_scale_y + contents_offset_y;
x2 = rect->x2 * contents_scale_x + contents_offset_x + 0.5;
y2 = rect->y2 * contents_scale_y + contents_offset_y + 0.5;
x1 = MAX(0, x1);
y1 = MAX(0, y1);
x2 = MIN(host->contents_width, x2);
y2 = MIN(host->contents_height, y2);
if (x1 < x2 && y1 < y2) {
size_t i;
for (i = 0; i < num_planes; ++i) {
uint8_t* src_base = src_addr + src_offset[i];
uint8_t* dst_base = dst_addr + dst_offset[i];
uint8_t* src = src_base + y1 * src_stride[i] + x1 * bpp;
uint8_t* dst = dst_base + y1 * dst_stride[i] + x1 * bpp;
int32_t width = x2 - x1;
int32_t height = (y2 - y1) / y_ss[i];
size_t bytes = width * bpp;
while (height--) {
memcpy(dst, src, bytes);
dst += dst_stride[i];
src += src_stride[i];
}
}
}
++rect;
}
if (host->current_buffer->mmap->end_write)
host->current_buffer->mmap->end_write(host->current_buffer->mmap->fd);
pixman_region32_clear(&host->current_buffer->damage);
wl_list_remove(&host->current_buffer->link);
wl_list_insert(&host->busy_buffers, &host->current_buffer->link);
}
if (host->contents_width && host->contents_height) {
double scale = host->ctx->scale * host->contents_scale;
if (host->viewport) {
int width = host->contents_width;
int height = host->contents_height;
// We need to take the client's viewport into account while still
// making sure our scale is accounted for.
if (viewport) {
if (viewport->src_x >= 0 && viewport->src_y >= 0 &&
viewport->src_width >= 0 && viewport->src_height >= 0) {
wp_viewport_set_source(host->viewport, viewport->src_x,
viewport->src_y, viewport->src_width,
viewport->src_height);
// If the source rectangle is set and the destination size is not
// set, then src_width and src_height should be integers, and the
// surface size becomes the source rectangle size.
width = wl_fixed_to_int(viewport->src_width);
height = wl_fixed_to_int(viewport->src_height);
}
// Use destination size as surface size when set.
if (viewport->dst_width >= 0 && viewport->dst_height >= 0) {
width = viewport->dst_width;
height = viewport->dst_height;
}
}
wp_viewport_set_destination(host->viewport, ceil(width / scale),
ceil(height / scale));
} else {
wl_surface_set_buffer_scale(host->proxy, scale);
}
}
// No need to defer client commits if surface has a role. E.g. is a cursor
// or shell surface.
if (host->has_role) {
wl_surface_commit(host->proxy);
// GTK determines the scale based on the output the surface has entered.
// If the surface has not entered any output, then have it enter the
// internal output. TODO(reveman): Remove this when surface-output tracking
// has been implemented in Chrome.
if (!host->has_output) {
struct sl_host_output* output;
wl_list_for_each(output, &host->ctx->host_outputs, link) {
if (output->internal) {
wl_surface_send_enter(host->resource, output->resource);
host->has_output = 1;
break;
}
}
}
} else {
// Commit if surface is associated with a window. Otherwise, defer
// commit until window is created.
wl_list_for_each(window, &host->ctx->windows, link) {
if (window->host_surface_id == wl_resource_get_id(resource)) {
if (window->xdg_surface) {
wl_surface_commit(host->proxy);
if (host->contents_width && host->contents_height)
window->realized = 1;
}
break;
}
}
}
if (host->contents_shm_mmap) {
if (host->contents_shm_mmap->buffer_resource)
wl_buffer_send_release(host->contents_shm_mmap->buffer_resource);
sl_mmap_unref(host->contents_shm_mmap);
host->contents_shm_mmap = NULL;
}
}
static void sl_host_surface_set_buffer_transform(struct wl_client* client,
struct wl_resource* resource,
int32_t transform) {
struct sl_host_surface* host = wl_resource_get_user_data(resource);
wl_surface_set_buffer_transform(host->proxy, transform);
}
static void sl_host_surface_set_buffer_scale(struct wl_client* client,
struct wl_resource* resource,
int32_t scale) {
struct sl_host_surface* host = wl_resource_get_user_data(resource);
host->contents_scale = scale;
}
static void sl_host_surface_damage_buffer(struct wl_client* client,
struct wl_resource* resource,
int32_t x,
int32_t y,
int32_t width,
int32_t height) {
assert(0);
}
static const struct wl_surface_interface sl_surface_implementation = {
sl_host_surface_destroy,
sl_host_surface_attach,
sl_host_surface_damage,
sl_host_surface_frame,
sl_host_surface_set_opaque_region,
sl_host_surface_set_input_region,
sl_host_surface_commit,
sl_host_surface_set_buffer_transform,
sl_host_surface_set_buffer_scale,
sl_host_surface_damage_buffer};
static void sl_destroy_host_surface(struct wl_resource* resource) {
struct sl_host_surface* host = wl_resource_get_user_data(resource);
struct sl_window *window, *surface_window = NULL;
struct sl_output_buffer* buffer;
wl_list_for_each(window, &host->ctx->windows, link) {
if (window->host_surface_id == wl_resource_get_id(resource)) {
surface_window = window;
break;
}
}
if (surface_window) {
surface_window->host_surface_id = 0;
sl_window_update(surface_window);
}
if (host->contents_shm_mmap)
sl_mmap_unref(host->contents_shm_mmap);
while (!wl_list_empty(&host->released_buffers)) {
buffer = wl_container_of(host->released_buffers.next, buffer, link);
sl_output_buffer_destroy(buffer);
}
while (!wl_list_empty(&host->busy_buffers)) {
buffer = wl_container_of(host->busy_buffers.next, buffer, link);
sl_output_buffer_destroy(buffer);
}
while (!wl_list_empty(&host->contents_viewport))
wl_list_remove(host->contents_viewport.next);
if (host->viewport)
wp_viewport_destroy(host->viewport);
wl_surface_destroy(host->proxy);
wl_resource_set_user_data(resource, NULL);
free(host);
}
static void sl_surface_enter(void* data,
struct wl_surface* surface,
struct wl_output* output) {
struct sl_host_surface* host = wl_surface_get_user_data(surface);
struct sl_host_output* host_output = wl_output_get_user_data(output);
wl_surface_send_enter(host->resource, host_output->resource);
host->has_output = 1;
}
static void sl_surface_leave(void* data,
struct wl_surface* surface,
struct wl_output* output) {
struct sl_host_surface* host = wl_surface_get_user_data(surface);
struct sl_host_output* host_output = wl_output_get_user_data(output);
wl_surface_send_leave(host->resource, host_output->resource);
}
static const struct wl_surface_listener sl_surface_listener = {
sl_surface_enter, sl_surface_leave};
static void sl_region_destroy(struct wl_client* client,
struct wl_resource* resource) {
wl_resource_destroy(resource);
}
static void sl_region_add(struct wl_client* client,
struct wl_resource* resource,
int32_t x,
int32_t y,
int32_t width,
int32_t height) {
struct sl_host_region* host = wl_resource_get_user_data(resource);
double scale = host->ctx->scale;
int32_t x1, y1, x2, y2;
x1 = x / scale;
y1 = y / scale;
x2 = (x + width) / scale;
y2 = (y + height) / scale;
wl_region_add(host->proxy, x1, y1, x2 - x1, y2 - y1);
}
static void sl_region_subtract(struct wl_client* client,
struct wl_resource* resource,
int32_t x,
int32_t y,
int32_t width,
int32_t height) {
struct sl_host_region* host = wl_resource_get_user_data(resource);
double scale = host->ctx->scale;
int32_t x1, y1, x2, y2;
x1 = x / scale;
y1 = y / scale;
x2 = (x + width) / scale;
y2 = (y + height) / scale;
wl_region_subtract(host->proxy, x1, y1, x2 - x1, y2 - y1);
}
static const struct wl_region_interface sl_region_implementation = {
sl_region_destroy, sl_region_add, sl_region_subtract};
static void sl_destroy_host_region(struct wl_resource* resource) {
struct sl_host_region* host = wl_resource_get_user_data(resource);
wl_region_destroy(host->proxy);
wl_resource_set_user_data(resource, NULL);
free(host);
}
static void sl_compositor_create_host_surface(struct wl_client* client,
struct wl_resource* resource,
uint32_t id) {
struct sl_host_compositor* host = wl_resource_get_user_data(resource);
struct sl_host_surface* host_surface;
struct sl_window *window, *unpaired_window = NULL;
host_surface = malloc(sizeof(*host_surface));
assert(host_surface);
host_surface->ctx = host->compositor->ctx;
host_surface->contents_width = 0;
host_surface->contents_height = 0;
host_surface->contents_scale = 1;
wl_list_init(&host_surface->contents_viewport);
host_surface->contents_shm_mmap = NULL;
host_surface->has_role = 0;
host_surface->has_output = 0;
host_surface->last_event_serial = 0;
host_surface->current_buffer = NULL;
wl_list_init(&host_surface->released_buffers);
wl_list_init(&host_surface->busy_buffers);
host_surface->resource = wl_resource_create(
client, &wl_surface_interface, wl_resource_get_version(resource), id);
wl_resource_set_implementation(host_surface->resource,
&sl_surface_implementation, host_surface,
sl_destroy_host_surface);
host_surface->proxy = wl_compositor_create_surface(host->proxy);
wl_surface_set_user_data(host_surface->proxy, host_surface);
wl_surface_add_listener(host_surface->proxy, &sl_surface_listener,
host_surface);
host_surface->viewport = NULL;
if (host_surface->ctx->viewporter) {
host_surface->viewport = wp_viewporter_get_viewport(
host_surface->ctx->viewporter->internal, host_surface->proxy);
}
wl_list_for_each(window, &host->compositor->ctx->unpaired_windows, link) {
if (window->host_surface_id == id) {
unpaired_window = window;
break;
}
}
if (unpaired_window)
sl_window_update(window);
}
static void sl_compositor_create_host_region(struct wl_client* client,
struct wl_resource* resource,
uint32_t id) {
struct sl_host_compositor* host = wl_resource_get_user_data(resource);
struct sl_host_region* host_region;
host_region = malloc(sizeof(*host_region));
assert(host_region);
host_region->ctx = host->compositor->ctx;
host_region->resource = wl_resource_create(
client, &wl_region_interface, wl_resource_get_version(resource), id);
wl_resource_set_implementation(host_region->resource,
&sl_region_implementation, host_region,
sl_destroy_host_region);
host_region->proxy = wl_compositor_create_region(host->proxy);
wl_region_set_user_data(host_region->proxy, host_region);
}
static const struct wl_compositor_interface sl_compositor_implementation = {
sl_compositor_create_host_surface, sl_compositor_create_host_region};
static void sl_destroy_host_compositor(struct wl_resource* resource) {
struct sl_host_compositor* host = wl_resource_get_user_data(resource);
wl_compositor_destroy(host->proxy);
wl_resource_set_user_data(resource, NULL);
free(host);
}
static void sl_bind_host_compositor(struct wl_client* client,
void* data,
uint32_t version,
uint32_t id) {
struct sl_context* ctx = (struct sl_context*)data;
struct sl_host_compositor* host;
host = malloc(sizeof(*host));
assert(host);
host->compositor = ctx->compositor;
host->resource =
wl_resource_create(client, &wl_compositor_interface,
MIN(version, ctx->compositor->version), id);
wl_resource_set_implementation(host->resource, &sl_compositor_implementation,
host, sl_destroy_host_compositor);
host->proxy = wl_registry_bind(wl_display_get_registry(ctx->display),
ctx->compositor->id, &wl_compositor_interface,
ctx->compositor->version);
wl_compositor_set_user_data(host->proxy, host);
}
struct sl_global* sl_compositor_global_create(struct sl_context* ctx) {
return sl_global_create(ctx, &wl_compositor_interface,
ctx->compositor->version, ctx,
sl_bind_host_compositor);
}

423
sommelier/sommelier-ctx.cc Normal file
View File

@ -0,0 +1,423 @@
// Copyright 2021 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "sommelier-ctx.h" // NOLINT(build/include_directory)
#include <assert.h>
#include <cerrno>
#include <string>
#include <sys/socket.h>
#include <unistd.h>
#include <wayland-util.h>
#include "aura-shell-client-protocol.h" // NOLINT(build/include_directory)
#include "sommelier.h" // NOLINT(build/include_directory)
#include "sommelier-tracing.h" // NOLINT(build/include_directory)
// TODO(b/173147612): Use container_token rather than this name.
#define DEFAULT_VM_NAME "termina"
// Returns the string mapped to the given ATOM_ enum value.
//
// Note this is NOT the atom value sent via the X protocol, despite both being
// ints. Use |sl_context::atoms| to map between X protocol atoms and ATOM_ enum
// values: If `atoms[i].value = j`, i is the ATOM_ enum value and j is the
// X protocol atom.
//
// If the given value is out of range of the ATOM_ enum, returns NULL.
const char* sl_context_atom_name(int atom_enum) {
switch (atom_enum) {
case ATOM_WM_S0:
return "WM_S0";
case ATOM_WM_PROTOCOLS:
return "WM_PROTOCOLS";
case ATOM_WM_STATE:
return "WM_STATE";
case ATOM_WM_CHANGE_STATE:
return "WM_CHANGE_STATE";
case ATOM_WM_DELETE_WINDOW:
return "WM_DELETE_WINDOW";
case ATOM_WM_TAKE_FOCUS:
return "WM_TAKE_FOCUS";
case ATOM_WM_CLIENT_LEADER:
return "WM_CLIENT_LEADER";
case ATOM_WL_SURFACE_ID:
return "WL_SURFACE_ID";
case ATOM_UTF8_STRING:
return "UTF8_STRING";
case ATOM_MOTIF_WM_HINTS:
return "_MOTIF_WM_HINTS";
case ATOM_NET_ACTIVE_WINDOW:
return "_NET_ACTIVE_WINDOW";
case ATOM_NET_FRAME_EXTENTS:
return "_NET_FRAME_EXTENTS";
case ATOM_NET_STARTUP_ID:
return "_NET_STARTUP_ID";
case ATOM_NET_SUPPORTED:
return "_NET_SUPPORTED";
case ATOM_NET_SUPPORTING_WM_CHECK:
return "_NET_SUPPORTING_WM_CHECK";
case ATOM_NET_WM_NAME:
return "_NET_WM_NAME";
case ATOM_NET_WM_MOVERESIZE:
return "_NET_WM_MOVERESIZE";
case ATOM_NET_WM_STATE:
return "_NET_WM_STATE";
case ATOM_NET_WM_STATE_FULLSCREEN:
return "_NET_WM_STATE_FULLSCREEN";
case ATOM_NET_WM_STATE_MAXIMIZED_VERT:
return "_NET_WM_STATE_MAXIMIZED_VERT";
case ATOM_NET_WM_STATE_MAXIMIZED_HORZ:
return "_NET_WM_STATE_MAXIMIZED_HORZ";
case ATOM_NET_WM_STATE_FOCUSED:
return "_NET_WM_STATE_FOCUSED";
case ATOM_CLIPBOARD:
return "CLIPBOARD";
case ATOM_CLIPBOARD_MANAGER:
return "CLIPBOARD_MANAGER";
case ATOM_TARGETS:
return "TARGETS";
case ATOM_TIMESTAMP:
return "TIMESTAMP";
case ATOM_TEXT:
return "TEXT";
case ATOM_INCR:
return "INCR";
case ATOM_WL_SELECTION:
return "_WL_SELECTION";
case ATOM_GTK_THEME_VARIANT:
return "_GTK_THEME_VARIANT";
case ATOM_XWAYLAND_RANDR_EMU_MONITOR_RECTS:
return "_XWAYLAND_RANDR_EMU_MONITOR_RECTS";
}
return NULL;
}
void sl_context_init_default(struct sl_context* ctx) {
*ctx = {0};
ctx->runprog = NULL;
ctx->display = NULL;
ctx->host_display = NULL;
ctx->client = NULL;
ctx->compositor = NULL;
ctx->subcompositor = NULL;
ctx->shm = NULL;
ctx->shell = NULL;
ctx->data_device_manager = NULL;
ctx->xdg_shell = NULL;
ctx->aura_shell = NULL;
ctx->viewporter = NULL;
ctx->linux_dmabuf = NULL;
ctx->keyboard_extension = NULL;
ctx->text_input_manager = NULL;
ctx->text_input_extension = NULL;
ctx->xdg_output_manager = NULL;
#ifdef GAMEPAD_SUPPORT
ctx->gaming_input_manager = NULL;
#endif
ctx->display_event_source = NULL;
ctx->display_ready_event_source = NULL;
ctx->sigchld_event_source = NULL;
ctx->sigusr1_event_source = NULL;
ctx->wm_fd = -1;
ctx->wayland_channel_fd = -1;
ctx->virtwl_socket_fd = -1;
ctx->virtwl_display_fd = -1;
ctx->wayland_channel_event_source = NULL;
ctx->virtwl_socket_event_source = NULL;
ctx->vm_id = DEFAULT_VM_NAME;
ctx->drm_device = NULL;
ctx->gbm = NULL;
ctx->xwayland = 0;
ctx->xwayland_pid = -1;
ctx->child_pid = -1;
ctx->peer_pid = -1;
ctx->xkb_context = NULL;
ctx->next_global_id = 1;
ctx->connection = NULL;
ctx->connection_event_source = NULL;
ctx->xfixes_extension = NULL;
ctx->screen = NULL;
ctx->window = 0;
ctx->host_focus_window = NULL;
ctx->needs_set_input_focus = 0;
ctx->desired_scale = 1.0;
ctx->scale = 1.0;
ctx->virt_scale_x = 1.0;
ctx->virt_scale_y = 1.0;
ctx->xdg_scale_x = 1.0;
ctx->xdg_scale_y = 1.0;
ctx->application_id = NULL;
ctx->application_id_property_name = NULL;
ctx->exit_with_child = 1;
ctx->sd_notify = NULL;
ctx->clipboard_manager = 0;
ctx->frame_color = 0xffffffff;
ctx->dark_frame_color = 0xff000000;
ctx->support_damage_buffer = true;
ctx->fullscreen_mode = ZAURA_SURFACE_FULLSCREEN_MODE_IMMERSIVE;
ctx->default_seat = NULL;
ctx->selection_window = XCB_WINDOW_NONE;
ctx->selection_owner = XCB_WINDOW_NONE;
ctx->selection_incremental_transfer = 0;
ctx->selection_request.requestor = XCB_NONE;
ctx->selection_request.property = XCB_ATOM_NONE;
ctx->selection_timestamp = XCB_CURRENT_TIME;
ctx->selection_data_device = NULL;
ctx->selection_data_offer = NULL;
ctx->selection_data_source = NULL;
ctx->selection_data_source_send_fd = -1;
ctx->selection_send_event_source = NULL;
ctx->selection_property_reply = NULL;
ctx->selection_property_offset = 0;
ctx->selection_event_source = NULL;
ctx->selection_data_offer_receive_fd = -1;
ctx->selection_data_ack_pending = 0;
for (unsigned i = 0; i < ARRAY_SIZE(ctx->atoms); i++) {
const char* name = sl_context_atom_name(i);
assert(name != NULL);
ctx->atoms[i].name = name;
}
ctx->timing = NULL;
ctx->trace_filename = NULL;
ctx->enable_xshape = false;
ctx->trace_system = false;
ctx->use_direct_scale = false;
wl_list_init(&ctx->accelerators);
wl_list_init(&ctx->windowed_accelerators);
wl_list_init(&ctx->registries);
wl_list_init(&ctx->globals);
wl_list_init(&ctx->outputs);
wl_list_init(&ctx->seats);
wl_list_init(&ctx->windows);
wl_list_init(&ctx->unpaired_windows);
wl_list_init(&ctx->host_outputs);
wl_list_init(&ctx->selection_data_source_send_pending);
#ifdef GAMEPAD_SUPPORT
wl_list_init(&ctx->gamepads);
#endif
}
static int sl_handle_clipboard_event(int fd, uint32_t mask, void* data) {
int rv;
struct sl_context* ctx = (struct sl_context*)data;
bool readable = false;
bool hang_up = false;
if (mask & WL_EVENT_READABLE)
readable = true;
if (mask & WL_EVENT_HANGUP)
hang_up = true;
rv = ctx->channel->handle_pipe(fd, readable, hang_up);
if (rv) {
fprintf(stderr, "reading pipe failed with %s\n", strerror(rv));
return 0;
}
if (hang_up) {
ctx->clipboard_event_source.reset();
return 0;
}
return 1;
}
static int sl_handle_wayland_channel_event(int fd, uint32_t mask, void* data) {
TRACE_EVENT("surface", "sl_handle_wayland_channel_event");
struct sl_context* ctx = (struct sl_context*)data;
struct WaylandSendReceive receive = {0};
int pipe_read_fd = -1;
enum WaylandChannelEvent event_type = WaylandChannelEvent::None;
char fd_buffer[CMSG_LEN(sizeof(int) * WAYLAND_MAX_FDs)];
struct msghdr msg = {0};
struct iovec buffer_iov;
ssize_t bytes;
int rv;
if (!(mask & WL_EVENT_READABLE)) {
fprintf(stderr,
"Got error or hangup on virtwl ctx fd"
" (mask %d), exiting\n",
mask);
exit(EXIT_SUCCESS);
}
receive.channel_fd = fd;
rv = ctx->channel->handle_channel_event(event_type, receive, pipe_read_fd);
if (rv) {
close(ctx->virtwl_socket_fd);
ctx->virtwl_socket_fd = -1;
return 0;
}
if (event_type == WaylandChannelEvent::ReceiveAndProxy) {
struct wl_event_loop* event_loop =
wl_display_get_event_loop(ctx->host_display);
ctx->clipboard_event_source.reset(
wl_event_loop_add_fd(event_loop, pipe_read_fd, WL_EVENT_READABLE,
sl_handle_clipboard_event, ctx));
} else if (event_type != WaylandChannelEvent::Receive) {
return 1;
}
buffer_iov.iov_base = receive.data;
buffer_iov.iov_len = receive.data_size;
msg.msg_iov = &buffer_iov;
msg.msg_iovlen = 1;
msg.msg_control = fd_buffer;
if (receive.num_fds) {
struct cmsghdr* cmsg;
// Need to set msg_controllen so CMSG_FIRSTHDR will return the first
// cmsghdr. We copy every fd we just received from the ioctl into this
// cmsghdr.
msg.msg_controllen = sizeof(fd_buffer);
cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
cmsg->cmsg_len = CMSG_LEN(receive.num_fds * sizeof(int));
memcpy(CMSG_DATA(cmsg), receive.fds, receive.num_fds * sizeof(int));
msg.msg_controllen = cmsg->cmsg_len;
}
bytes = sendmsg(ctx->virtwl_socket_fd, &msg, MSG_NOSIGNAL);
errno_assert(bytes == static_cast<ssize_t>(receive.data_size));
while (receive.num_fds--)
close(receive.fds[receive.num_fds]);
if (receive.data)
free(receive.data);
return 1;
}
static int sl_handle_virtwl_socket_event(int fd, uint32_t mask, void* data) {
TRACE_EVENT("surface", "sl_handle_virtwl_socket_event");
struct sl_context* ctx = (struct sl_context*)data;
struct WaylandSendReceive send = {0};
char fd_buffer[CMSG_LEN(sizeof(int) * WAYLAND_MAX_FDs)];
uint8_t data_buffer[DEFAULT_BUFFER_SIZE];
struct iovec buffer_iov;
struct msghdr msg = {0};
struct cmsghdr* cmsg;
ssize_t bytes;
int rv;
if (!(mask & WL_EVENT_READABLE)) {
fprintf(stderr,
"Got error or hangup on virtwl socket"
" (mask %d), exiting\n",
mask);
exit(EXIT_SUCCESS);
}
buffer_iov.iov_base = data_buffer;
buffer_iov.iov_len = ctx->channel->max_send_size();
msg.msg_iov = &buffer_iov;
msg.msg_iovlen = 1;
msg.msg_control = fd_buffer;
msg.msg_controllen = sizeof(fd_buffer);
bytes = recvmsg(ctx->virtwl_socket_fd, &msg, 0);
errno_assert(bytes > 0);
// If there were any FDs recv'd by recvmsg, there will be some data in the
// msg_control buffer. To get the FDs out we iterate all cmsghdr's within and
// unpack the FDs if the cmsghdr type is SCM_RIGHTS.
for (cmsg = msg.msg_controllen != 0 ? CMSG_FIRSTHDR(&msg) : NULL; cmsg;
cmsg = CMSG_NXTHDR(&msg, cmsg)) {
size_t cmsg_fd_count;
if (cmsg->cmsg_level != SOL_SOCKET || cmsg->cmsg_type != SCM_RIGHTS)
continue;
cmsg_fd_count = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int);
// fd_count will never exceed WAYLAND_MAX_FDs because the
// control message buffer only allocates enough space for that many FDs.
memcpy(&send.fds[send.num_fds], CMSG_DATA(cmsg),
cmsg_fd_count * sizeof(int));
send.num_fds += cmsg_fd_count;
}
send.channel_fd = ctx->wayland_channel_fd;
send.data = data_buffer;
send.data_size = bytes;
rv = ctx->channel->send(send);
errno_assert(!rv);
while (send.num_fds--)
close(send.fds[send.num_fds]);
return 1;
}
bool sl_context_init_wayland_channel(struct sl_context* ctx,
struct wl_event_loop* event_loop,
bool display) {
if (ctx->channel == NULL) {
// Running in noop mode, without virtualization.
return true;
}
int rv = ctx->channel->init();
if (rv) {
fprintf(stderr, "error: could not initialize wayland channel: %s\n",
strerror(-rv));
return false;
}
if (!display) {
// We use a wayland virtual context unless display was explicitly specified.
// WARNING: It's critical that we never call wl_display_roundtrip
// as we're not spawning a new thread to handle forwarding. Calling
// wl_display_roundtrip will cause a deadlock.
int vws[2];
// Connection to virtwl channel.
rv = socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, vws);
errno_assert(!rv);
ctx->virtwl_socket_fd = vws[0];
ctx->virtwl_display_fd = vws[1];
rv = ctx->channel->create_context(ctx->wayland_channel_fd);
if (rv) {
fprintf(stderr, "error: failed to create virtwl context: %s\n",
strerror(-rv));
return false;
}
ctx->virtwl_socket_event_source.reset(wl_event_loop_add_fd(
event_loop, ctx->virtwl_socket_fd, WL_EVENT_READABLE,
sl_handle_virtwl_socket_event, ctx));
ctx->wayland_channel_event_source.reset(wl_event_loop_add_fd(
event_loop, ctx->wayland_channel_fd, WL_EVENT_READABLE,
sl_handle_wayland_channel_event, ctx));
}
return true;
}
sl_window* sl_context_lookup_window_for_surface(struct sl_context* ctx,
wl_resource* resource) {
sl_window* surface_window = NULL;
sl_window* window;
wl_list_for_each(window, &ctx->windows, link) {
if (window->host_surface_id == wl_resource_get_id(resource)) {
surface_window = window;
break;
}
}
return surface_window;
}

215
sommelier/sommelier-ctx.h Normal file
View File

@ -0,0 +1,215 @@
// Copyright 2021 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef VM_TOOLS_SOMMELIER_SOMMELIER_CTX_H_
#define VM_TOOLS_SOMMELIER_SOMMELIER_CTX_H_
#include <memory>
#include <string>
#include <wayland-server.h>
#include <wayland-util.h>
#include <xcb/xcb.h>
#include <limits.h>
#include "sommelier-timing.h" // NOLINT(build/include_directory)
#include "sommelier-util.h" // NOLINT(build/include_directory)
#include "virtualization/wayland_channel.h"
#define MIN(a, b) (((a) < (b)) ? (a) : (b))
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
#define MIN_SIZE (INT_MIN / 10)
#define MAX_SIZE (INT_MAX / 10)
// A list of atoms to intern (create/fetch) when connecting to the X server.
//
// To add an atom, declare it here and define it in |sl_context_atom_name|.
enum {
ATOM_WM_S0,
ATOM_WM_PROTOCOLS,
ATOM_WM_STATE,
ATOM_WM_CHANGE_STATE,
ATOM_WM_DELETE_WINDOW,
ATOM_WM_TAKE_FOCUS,
ATOM_WM_CLIENT_LEADER,
ATOM_WL_SURFACE_ID,
ATOM_UTF8_STRING,
ATOM_MOTIF_WM_HINTS,
ATOM_NET_ACTIVE_WINDOW,
ATOM_NET_FRAME_EXTENTS,
ATOM_NET_STARTUP_ID,
ATOM_NET_SUPPORTED,
ATOM_NET_SUPPORTING_WM_CHECK,
ATOM_NET_WM_NAME,
ATOM_NET_WM_MOVERESIZE,
ATOM_NET_WM_STATE,
ATOM_NET_WM_STATE_FULLSCREEN,
ATOM_NET_WM_STATE_MAXIMIZED_VERT,
ATOM_NET_WM_STATE_MAXIMIZED_HORZ,
ATOM_NET_WM_STATE_FOCUSED,
ATOM_CLIPBOARD,
ATOM_CLIPBOARD_MANAGER,
ATOM_TARGETS,
ATOM_TIMESTAMP,
ATOM_TEXT,
ATOM_INCR,
ATOM_WL_SELECTION,
ATOM_GTK_THEME_VARIANT,
ATOM_XWAYLAND_RANDR_EMU_MONITOR_RECTS,
ATOM_LAST = ATOM_XWAYLAND_RANDR_EMU_MONITOR_RECTS,
};
struct sl_context {
char** runprog;
struct wl_display* display;
struct wl_display* host_display;
struct wl_client* client;
struct sl_compositor* compositor;
struct sl_subcompositor* subcompositor;
struct sl_shm* shm;
struct sl_shell* shell;
struct sl_data_device_manager* data_device_manager;
struct sl_xdg_shell* xdg_shell;
struct sl_aura_shell* aura_shell;
struct sl_viewporter* viewporter;
struct sl_linux_dmabuf* linux_dmabuf;
struct sl_linux_explicit_synchronization* linux_explicit_synchronization;
struct sl_keyboard_extension* keyboard_extension;
struct sl_text_input_manager* text_input_manager;
struct sl_text_input_extension* text_input_extension;
struct sl_xdg_output_manager* xdg_output_manager;
#ifdef GAMEPAD_SUPPORT
struct sl_gaming_input_manager* gaming_input_manager;
#endif
struct sl_relative_pointer_manager* relative_pointer_manager;
struct sl_pointer_constraints* pointer_constraints;
struct wl_list outputs;
struct wl_list seats;
std::unique_ptr<struct wl_event_source> display_event_source;
std::unique_ptr<struct wl_event_source> display_ready_event_source;
std::unique_ptr<struct wl_event_source> sigchld_event_source;
std::unique_ptr<struct wl_event_source> sigusr1_event_source;
std::unique_ptr<struct wl_event_source> clipboard_event_source;
struct wl_array dpi;
int wm_fd;
int wayland_channel_fd;
int virtwl_socket_fd;
int virtwl_display_fd;
std::unique_ptr<struct wl_event_source> wayland_channel_event_source;
std::unique_ptr<struct wl_event_source> virtwl_socket_event_source;
const char* drm_device;
struct gbm_device* gbm;
int xwayland;
pid_t xwayland_pid;
// XWayland-hosting sommelier instances allow additional connections for IME
// support.
wl_listener extra_client_created_listener;
pid_t child_pid;
pid_t peer_pid;
struct xkb_context* xkb_context;
struct wl_list accelerators;
struct wl_list windowed_accelerators;
struct wl_list registries;
struct wl_list globals;
struct wl_list host_outputs;
int next_global_id;
xcb_connection_t* connection;
std::unique_ptr<struct wl_event_source> connection_event_source;
const xcb_query_extension_reply_t* xfixes_extension;
const xcb_query_extension_reply_t* xshape_extension;
xcb_screen_t* screen;
xcb_window_t window;
struct wl_list windows, unpaired_windows;
struct sl_window* host_focus_window;
int needs_set_input_focus;
#ifdef GAMEPAD_SUPPORT
struct wl_list gamepads;
#endif
double desired_scale;
double scale;
// These scale factors are used for the direct scaling mode.
// These factors are set to the values computed from the internal/default
// display.
// See sommelier-transform.h and the definition in sl_output
// for more details on this.
double virt_scale_x, virt_scale_y;
double xdg_scale_x, xdg_scale_y;
// If non-null, all X11 client apps will be given this application ID.
const char* application_id;
// VM name embedded in application IDs, if application_id is null.
const char* vm_id;
// A custom X11 property to append to application IDs, if application_id
// is null. Used in preference to other X11 properties such as WM_CLASS.
const char* application_id_property_name;
xcb_atom_t application_id_property_atom = XCB_ATOM_NONE;
int exit_with_child;
const char* sd_notify;
int clipboard_manager;
uint32_t frame_color;
uint32_t dark_frame_color;
bool support_damage_buffer;
int fullscreen_mode;
struct sl_host_seat* default_seat;
xcb_window_t selection_window;
xcb_window_t selection_owner;
int selection_incremental_transfer;
xcb_selection_request_event_t selection_request;
xcb_timestamp_t selection_timestamp;
struct wl_data_device* selection_data_device;
struct sl_data_offer* selection_data_offer;
struct sl_data_source* selection_data_source;
int selection_data_source_send_fd;
struct wl_list selection_data_source_send_pending;
std::unique_ptr<struct wl_event_source> selection_send_event_source;
xcb_get_property_reply_t* selection_property_reply;
int selection_property_offset;
std::unique_ptr<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];
Timing* timing;
const char* trace_filename;
bool enable_xshape;
bool trace_system;
bool use_explicit_fence;
bool use_virtgpu_channel;
bool use_direct_scale;
// Never freed after allocation due the fact sommelier doesn't have a
// shutdown function yet.
WaylandChannel* channel;
};
// Returns the string mapped to the given ATOM_ enum value.
//
// Note this is NOT the atom value sent via the X protocol, despite both being
// ints. Use |sl_context::atoms| to map between X protocol atoms and ATOM_ enum
// values: If `atoms[i].value = j`, i is the ATOM_ enum value and j is the
// X protocol atom.
//
// If the given value is out of range of the ATOM_ enum, returns NULL.
const char* sl_context_atom_name(int atom_enum);
void sl_context_init_default(struct sl_context* ctx);
bool sl_context_init_wayland_channel(struct sl_context* ctx,
struct wl_event_loop* event_loop,
bool display);
sl_window* sl_context_lookup_window_for_surface(struct sl_context* ctx,
wl_resource* resource);
#endif // VM_TOOLS_SOMMELIER_SOMMELIER_CTX_H_

View File

@ -1,13 +1,13 @@
// Copyright 2018 The Chromium OS Authors. All rights reserved.
// Copyright 2018 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "sommelier.h"
#include "sommelier.h" // NOLINT(build/include_directory)
#include "sommelier-transform.h" // NOLINT(build/include_directory)
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <linux/virtwl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@ -25,37 +25,45 @@ struct sl_host_data_device {
struct sl_context* ctx;
struct wl_resource* resource;
struct wl_data_device* proxy;
struct sl_host_surface* focus_surface;
};
MAP_STRUCTS(wl_data_device, sl_host_data_device);
struct sl_host_data_source {
struct wl_resource* resource;
struct wl_data_source* proxy;
};
MAP_STRUCTS(wl_data_source, sl_host_data_source);
struct sl_host_data_offer {
struct sl_context* ctx;
struct wl_resource* resource;
struct wl_data_offer* proxy;
};
MAP_STRUCTS(wl_data_offer, sl_host_data_offer);
struct sl_data_transfer {
int read_fd;
int write_fd;
size_t offset;
size_t bytes_left;
uint8_t data[4096];
struct wl_event_source* read_event_source;
struct wl_event_source* write_event_source;
uint8_t data[DEFAULT_BUFFER_SIZE];
std::unique_ptr<struct wl_event_source> read_event_source;
std::unique_ptr<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);
int read_fd = transfer->read_fd;
int write_fd = transfer->write_fd;
// default deleter of wl_event_source removes the sources from their
// associated wl_event_loop, but we still want to close the fd. Ordered this
// way because we don't want any additional events associated with the close()
// calls to end up a the wl_event_loop.
delete transfer;
close(read_fd);
close(write_fd);
}
static int sl_handle_data_transfer_read(int fd, uint32_t mask, void* data) {
@ -88,8 +96,9 @@ static int sl_handle_data_transfer_read(int fd, uint32_t mask, void* data) {
transfer->offset = 0;
// There may still be data to read from the event source, but we have no
// room in our buffer so move to the writing state.
wl_event_source_fd_update(transfer->read_event_source, 0);
wl_event_source_fd_update(transfer->write_event_source, WL_EVENT_WRITABLE);
wl_event_source_fd_update(transfer->read_event_source.get(), 0);
wl_event_source_fd_update(transfer->write_event_source.get(),
WL_EVENT_WRITABLE);
} else {
// On a read error or EOF, end the transfer.
sl_data_transfer_destroy(transfer);
@ -121,15 +130,16 @@ static int sl_handle_data_transfer_write(int fd, uint32_t mask, void* data) {
// On a write error, end the transfer.
sl_data_transfer_destroy(transfer);
} else {
assert(rv <= transfer->bytes_left);
assert(rv <= static_cast<int>(transfer->bytes_left));
transfer->bytes_left -= rv;
transfer->offset += rv;
}
if (!transfer->bytes_left) {
// If all data has been written, move back to the reading state.
wl_event_source_fd_update(transfer->write_event_source, 0);
wl_event_source_fd_update(transfer->read_event_source, WL_EVENT_READABLE);
wl_event_source_fd_update(transfer->write_event_source.get(), 0);
wl_event_source_fd_update(transfer->read_event_source.get(),
WL_EVENT_READABLE);
}
// If there is still data left, continue in the writing state.
@ -139,7 +149,6 @@ static int sl_handle_data_transfer_write(int fd, uint32_t mask, void* data) {
static void sl_data_transfer_create(struct wl_event_loop* event_loop,
int read_fd,
int write_fd) {
struct sl_data_transfer* transfer;
int flags;
int rv;
@ -149,62 +158,46 @@ static void sl_data_transfer_create(struct wl_event_loop* event_loop,
UNUSED(rv);
// Start out the transfer in the reading state.
transfer = malloc(sizeof(*transfer));
struct sl_data_transfer* transfer = new sl_data_transfer;
assert(transfer);
transfer->read_fd = read_fd;
transfer->write_fd = write_fd;
transfer->offset = 0;
transfer->bytes_left = 0;
transfer->read_event_source =
memset(transfer->data, 0, DEFAULT_BUFFER_SIZE);
transfer->read_event_source.reset(
wl_event_loop_add_fd(event_loop, read_fd, WL_EVENT_READABLE,
sl_handle_data_transfer_read, transfer);
transfer->write_event_source = wl_event_loop_add_fd(
event_loop, write_fd, 0, sl_handle_data_transfer_write, transfer);
}
static void sl_data_offer_accept(struct wl_client* client,
struct wl_resource* resource,
uint32_t serial,
const char* mime_type) {
struct sl_host_data_offer* host = wl_resource_get_user_data(resource);
wl_data_offer_accept(host->proxy, serial, mime_type);
sl_handle_data_transfer_read, transfer));
transfer->write_event_source.reset(wl_event_loop_add_fd(
event_loop, write_fd, 0, sl_handle_data_transfer_write, transfer));
}
static void sl_data_offer_receive(struct wl_client* client,
struct wl_resource* resource,
const char* mime_type,
int32_t fd) {
struct sl_host_data_offer* host = wl_resource_get_user_data(resource);
struct sl_host_data_offer* host =
static_cast<sl_host_data_offer*>(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));
if (host->ctx->channel == NULL) {
// Running in noop mode, without virtualization.
wl_data_offer_receive(host->proxy, mime_type, fd);
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);
int pipe_fd, rv;
rv = host->ctx->channel->create_pipe(pipe_fd);
if (rv) {
fprintf(stderr, "error: failed to create virtwl pipe: %s\n", strerror(-rv));
close(fd);
break;
return;
}
sl_data_transfer_create(wl_display_get_event_loop(host->ctx->host_display),
pipe_fd, fd);
wl_data_offer_receive(host->proxy, mime_type, pipe_fd);
}
static void sl_data_offer_destroy(struct wl_client* client,
@ -212,30 +205,16 @@ static void sl_data_offer_destroy(struct wl_client* client,
wl_resource_destroy(resource);
}
static void sl_data_offer_finish(struct wl_client* client,
struct wl_resource* resource) {
struct sl_host_data_offer* host = wl_resource_get_user_data(resource);
wl_data_offer_finish(host->proxy);
}
static void sl_data_offer_set_actions(struct wl_client* client,
struct wl_resource* resource,
uint32_t dnd_actions,
uint32_t preferred_action) {
struct sl_host_data_offer* host = wl_resource_get_user_data(resource);
wl_data_offer_set_actions(host->proxy, dnd_actions, preferred_action);
}
static const struct wl_data_offer_interface sl_data_offer_implementation = {
sl_data_offer_accept, sl_data_offer_receive, sl_data_offer_destroy,
sl_data_offer_finish, sl_data_offer_set_actions};
ForwardRequest<wl_data_offer_accept>, sl_data_offer_receive,
sl_data_offer_destroy, ForwardRequest<wl_data_offer_finish>,
ForwardRequest<wl_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);
struct sl_host_data_offer* host =
static_cast<sl_host_data_offer*>(wl_data_offer_get_user_data(data_offer));
wl_data_offer_send_offer(host->resource, mime_type);
}
@ -243,7 +222,8 @@ static void sl_data_offer_offer(void* data,
static void sl_data_offer_source_actions(void* data,
struct wl_data_offer* data_offer,
uint32_t source_actions) {
struct sl_host_data_offer* host = wl_data_offer_get_user_data(data_offer);
struct sl_host_data_offer* host =
static_cast<sl_host_data_offer*>(wl_data_offer_get_user_data(data_offer));
wl_data_offer_send_source_actions(host->resource, source_actions);
}
@ -251,7 +231,8 @@ static void sl_data_offer_source_actions(void* data,
static void sl_data_offer_action(void* data,
struct wl_data_offer* data_offer,
uint32_t dnd_action) {
struct sl_host_data_offer* host = wl_data_offer_get_user_data(data_offer);
struct sl_host_data_offer* host =
static_cast<sl_host_data_offer*>(wl_data_offer_get_user_data(data_offer));
wl_data_offer_send_action(host->resource, dnd_action);
}
@ -260,19 +241,12 @@ static const struct wl_data_offer_listener sl_data_offer_listener = {
sl_data_offer_offer, sl_data_offer_source_actions, sl_data_offer_action};
static void sl_destroy_host_data_offer(struct wl_resource* resource) {
struct sl_host_data_offer* host = wl_resource_get_user_data(resource);
struct sl_host_data_offer* host =
static_cast<sl_host_data_offer*>(wl_resource_get_user_data(resource));
wl_data_offer_destroy(host->proxy);
wl_resource_set_user_data(resource, NULL);
free(host);
}
static void sl_data_source_offer(struct wl_client* client,
struct wl_resource* resource,
const char* mime_type) {
struct sl_host_data_source* host = wl_resource_get_user_data(resource);
wl_data_source_offer(host->proxy, mime_type);
delete host;
}
static void sl_data_source_destroy(struct wl_client* client,
@ -280,21 +254,15 @@ static void sl_data_source_destroy(struct wl_client* client,
wl_resource_destroy(resource);
}
static void sl_data_source_set_actions(struct wl_client* client,
struct wl_resource* resource,
uint32_t dnd_actions) {
struct sl_host_data_source* host = wl_resource_get_user_data(resource);
wl_data_source_set_actions(host->proxy, dnd_actions);
}
static const struct wl_data_source_interface sl_data_source_implementation = {
sl_data_source_offer, sl_data_source_destroy, sl_data_source_set_actions};
ForwardRequest<wl_data_source_offer>, sl_data_source_destroy,
ForwardRequest<wl_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);
struct sl_host_data_source* host = static_cast<sl_host_data_source*>(
wl_data_source_get_user_data(data_source));
wl_data_source_send_target(host->resource, mime_type);
}
@ -303,7 +271,8 @@ static void sl_data_source_send(void* data,
struct wl_data_source* data_source,
const char* mime_type,
int32_t fd) {
struct sl_host_data_source* host = wl_data_source_get_user_data(data_source);
struct sl_host_data_source* host = static_cast<sl_host_data_source*>(
wl_data_source_get_user_data(data_source));
wl_data_source_send_send(host->resource, mime_type, fd);
close(fd);
@ -311,21 +280,24 @@ static void sl_data_source_send(void* data,
static void sl_data_source_cancelled(void* data,
struct wl_data_source* data_source) {
struct sl_host_data_source* host = wl_data_source_get_user_data(data_source);
struct sl_host_data_source* host = static_cast<sl_host_data_source*>(
wl_data_source_get_user_data(data_source));
wl_data_source_send_cancelled(host->resource);
}
void sl_data_source_dnd_drop_performed(void* data,
struct wl_data_source* data_source) {
struct sl_host_data_source* host = wl_data_source_get_user_data(data_source);
struct sl_host_data_source* host = static_cast<sl_host_data_source*>(
wl_data_source_get_user_data(data_source));
wl_data_source_send_dnd_drop_performed(host->resource);
}
void sl_data_source_dnd_finished(void* data,
struct wl_data_source* data_source) {
struct sl_host_data_source* host = wl_data_source_get_user_data(data_source);
struct sl_host_data_source* host = static_cast<sl_host_data_source*>(
wl_data_source_get_user_data(data_source));
wl_data_source_send_dnd_finished(host->resource);
}
@ -333,7 +305,8 @@ void sl_data_source_dnd_finished(void* data,
void sl_data_source_actions(void* data,
struct wl_data_source* data_source,
uint32_t dnd_action) {
struct sl_host_data_source* host = wl_data_source_get_user_data(data_source);
struct sl_host_data_source* host = static_cast<sl_host_data_source*>(
wl_data_source_get_user_data(data_source));
wl_data_source_send_action(host->resource, dnd_action);
}
@ -344,11 +317,12 @@ static const struct wl_data_source_listener sl_data_source_listener = {
sl_data_source_dnd_finished, sl_data_source_actions};
static void sl_destroy_host_data_source(struct wl_resource* resource) {
struct sl_host_data_source* host = wl_resource_get_user_data(resource);
struct sl_host_data_source* host =
static_cast<sl_host_data_source*>(wl_resource_get_user_data(resource));
wl_data_source_destroy(host->proxy);
wl_resource_set_user_data(resource, NULL);
free(host);
delete host;
}
static void sl_data_device_start_drag(struct wl_client* client,
@ -357,50 +331,44 @@ static void sl_data_device_start_drag(struct wl_client* client,
struct wl_resource* origin_resource,
struct wl_resource* icon_resource,
uint32_t serial) {
struct sl_host_data_device* host = wl_resource_get_user_data(resource);
struct sl_host_data_device* host =
static_cast<sl_host_data_device*>(wl_resource_get_user_data(resource));
struct sl_host_data_source* host_source =
source_resource ? wl_resource_get_user_data(source_resource) : NULL;
source_resource ? static_cast<sl_host_data_source*>(
wl_resource_get_user_data(source_resource))
: NULL;
struct sl_host_surface* host_origin =
origin_resource ? wl_resource_get_user_data(origin_resource) : NULL;
origin_resource ? static_cast<sl_host_surface*>(
wl_resource_get_user_data(origin_resource))
: NULL;
struct sl_host_surface* host_icon =
icon_resource ? wl_resource_get_user_data(icon_resource) : NULL;
icon_resource ? static_cast<sl_host_surface*>(
wl_resource_get_user_data(icon_resource))
: NULL;
host_icon->has_role = 1;
wl_data_device_start_drag(host->proxy,
host_source ? host_source->proxy : NULL,
host_origin ? host_origin->proxy : NULL,
host_icon ? host_icon->proxy : NULL, serial);
}
static void sl_data_device_set_selection(struct wl_client* client,
struct wl_resource* resource,
struct wl_resource* source_resource,
uint32_t serial) {
struct sl_host_data_device* host = wl_resource_get_user_data(resource);
struct sl_host_data_source* host_source =
source_resource ? wl_resource_get_user_data(source_resource) : NULL;
wl_data_device_set_selection(host->proxy,
host_source ? host_source->proxy : NULL, serial);
}
} // NOLINT(whitespace/indent)
static void sl_data_device_release(struct wl_client* client,
struct wl_resource* resource) {
wl_resource_destroy(resource);
}
} // NOLINT(whitespace/indent)
static const struct wl_data_device_interface sl_data_device_implementation = {
sl_data_device_start_drag, sl_data_device_set_selection,
sl_data_device_start_drag,
ForwardRequest<wl_data_device_set_selection, AllowNullResource::kYes>,
sl_data_device_release};
static void sl_data_device_data_offer(void* data,
struct wl_data_device* data_device,
struct wl_data_offer* data_offer) {
struct sl_host_data_device* host = wl_data_device_get_user_data(data_device);
struct sl_host_data_offer* host_data_offer;
host_data_offer = malloc(sizeof(*host_data_offer));
assert(host_data_offer);
struct sl_host_data_device* host = static_cast<sl_host_data_device*>(
wl_data_device_get_user_data(data_device));
struct sl_host_data_offer* host_data_offer = new sl_host_data_offer();
host_data_offer->ctx = host->ctx;
host_data_offer->resource = wl_resource_create(
@ -410,7 +378,6 @@ static void sl_data_device_data_offer(void* data,
&sl_data_offer_implementation, host_data_offer,
sl_destroy_host_data_offer);
host_data_offer->proxy = data_offer;
wl_data_offer_set_user_data(host_data_offer->proxy, host_data_offer);
wl_data_offer_add_listener(host_data_offer->proxy, &sl_data_offer_listener,
host_data_offer);
@ -424,22 +391,28 @@ static void sl_data_device_enter(void* data,
wl_fixed_t x,
wl_fixed_t y,
struct wl_data_offer* data_offer) {
struct sl_host_data_device* host = wl_data_device_get_user_data(data_device);
struct sl_host_surface* host_surface = wl_surface_get_user_data(surface);
struct sl_host_data_device* host = static_cast<sl_host_data_device*>(
wl_data_device_get_user_data(data_device));
struct sl_host_surface* host_surface =
static_cast<sl_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;
static_cast<sl_host_data_offer*>(wl_data_offer_get_user_data(data_offer));
wl_fixed_t ix = x, iy = y;
wl_data_device_send_enter(host->resource, serial, host_surface->resource,
wl_fixed_from_double(wl_fixed_to_double(x) * scale),
wl_fixed_from_double(wl_fixed_to_double(y) * scale),
host_data_offer->resource);
}
sl_transform_host_to_guest_fixed(host->ctx, host_surface, &ix, &iy);
host->focus_surface = host_surface;
wl_data_device_send_enter(host->resource, serial, host_surface->resource, ix,
iy, host_data_offer->resource);
} // NOLINT(whitespace/indent)
static void sl_data_device_leave(void* data,
struct wl_data_device* data_device) {
struct sl_host_data_device* host = wl_data_device_get_user_data(data_device);
struct sl_host_data_device* host = static_cast<sl_host_data_device*>(
wl_data_device_get_user_data(data_device));
host->focus_surface = NULL;
wl_data_device_send_leave(host->resource);
}
@ -448,29 +421,36 @@ static void sl_data_device_motion(void* data,
uint32_t time,
wl_fixed_t x,
wl_fixed_t y) {
struct sl_host_data_device* host = wl_data_device_get_user_data(data_device);
double scale = host->ctx->scale;
struct sl_host_data_device* host = static_cast<sl_host_data_device*>(
wl_data_device_get_user_data(data_device));
wl_fixed_t ix = x, iy = y;
wl_data_device_send_motion(
host->resource, time, wl_fixed_from_double(wl_fixed_to_double(x) * scale),
wl_fixed_from_double(wl_fixed_to_double(y) * scale));
sl_transform_host_to_guest_fixed(host->ctx, host->focus_surface, &ix, &iy);
wl_data_device_send_motion(host->resource, time, ix, iy);
}
static void sl_data_device_drop(void* data,
struct wl_data_device* data_device) {
struct sl_host_data_device* host = wl_data_device_get_user_data(data_device);
struct sl_host_data_device* host = static_cast<sl_host_data_device*>(
wl_data_device_get_user_data(data_device));
host->focus_surface = NULL;
wl_data_device_send_drop(host->resource);
}
static void sl_data_device_selection(void* data,
struct wl_data_device* data_device,
struct wl_data_offer* data_offer) {
struct sl_host_data_device* host = wl_data_device_get_user_data(data_device);
struct sl_host_data_offer* host_data_offer =
wl_data_offer_get_user_data(data_offer);
struct sl_host_data_device* host = static_cast<sl_host_data_device*>(
wl_data_device_get_user_data(data_device));
struct wl_resource* data_offer_resource =
data_offer ? static_cast<sl_host_data_offer*>(
wl_data_offer_get_user_data(data_offer))
->resource
: nullptr;
wl_data_device_send_selection(host->resource, host_data_offer->resource);
wl_data_device_send_selection(host->resource, data_offer_resource);
}
static const struct wl_data_device_listener sl_data_device_listener = {
@ -478,7 +458,8 @@ static const struct wl_data_device_listener sl_data_device_listener = {
sl_data_device_motion, sl_data_device_drop, sl_data_device_selection};
static void sl_destroy_host_data_device(struct wl_resource* resource) {
struct sl_host_data_device* host = wl_resource_get_user_data(resource);
struct sl_host_data_device* host =
static_cast<sl_host_data_device*>(wl_resource_get_user_data(resource));
if (wl_data_device_get_version(host->proxy) >=
WL_DATA_DEVICE_RELEASE_SINCE_VERSION) {
@ -487,17 +468,15 @@ static void sl_destroy_host_data_device(struct wl_resource* resource) {
wl_data_device_destroy(host->proxy);
}
wl_resource_set_user_data(resource, NULL);
free(host);
delete host;
}
static void sl_data_device_manager_create_data_source(
struct wl_client* client, struct wl_resource* resource, uint32_t id) {
struct sl_host_data_device_manager* host =
wl_resource_get_user_data(resource);
struct sl_host_data_source* host_data_source;
host_data_source = malloc(sizeof(*host_data_source));
assert(host_data_source);
static_cast<sl_host_data_device_manager*>(
wl_resource_get_user_data(resource));
struct sl_host_data_source* host_data_source = new sl_host_data_source();
host_data_source->resource = wl_resource_create(
client, &wl_data_source_interface, wl_resource_get_version(resource), id);
@ -506,7 +485,6 @@ static void sl_data_device_manager_create_data_source(
host_data_source, sl_destroy_host_data_source);
host_data_source->proxy =
wl_data_device_manager_create_data_source(host->proxy);
wl_data_source_set_user_data(host_data_source->proxy, host_data_source);
wl_data_source_add_listener(host_data_source->proxy, &sl_data_source_listener,
host_data_source);
}
@ -517,14 +495,14 @@ static void sl_data_device_manager_get_data_device(
uint32_t id,
struct wl_resource* seat_resource) {
struct sl_host_data_device_manager* host =
wl_resource_get_user_data(resource);
struct sl_host_seat* host_seat = wl_resource_get_user_data(seat_resource);
struct sl_host_data_device* host_data_device;
host_data_device = malloc(sizeof(*host_data_device));
assert(host_data_device);
static_cast<sl_host_data_device_manager*>(
wl_resource_get_user_data(resource));
struct sl_host_seat* host_seat =
static_cast<sl_host_seat*>(wl_resource_get_user_data(seat_resource));
struct sl_host_data_device* host_data_device = new sl_host_data_device();
host_data_device->ctx = host->ctx;
host_data_device->focus_surface = NULL;
host_data_device->resource = wl_resource_create(
client, &wl_data_device_interface, wl_resource_get_version(resource), id);
wl_resource_set_implementation(host_data_device->resource,
@ -532,10 +510,9 @@ static void sl_data_device_manager_get_data_device(
host_data_device, sl_destroy_host_data_device);
host_data_device->proxy =
wl_data_device_manager_get_data_device(host->proxy, host_seat->proxy);
wl_data_device_set_user_data(host_data_device->proxy, host_data_device);
wl_data_device_add_listener(host_data_device->proxy, &sl_data_device_listener,
host_data_device);
}
} // NOLINT(whitespace/indent)
static const struct wl_data_device_manager_interface
sl_data_device_manager_implementation = {
@ -544,11 +521,12 @@ static const struct wl_data_device_manager_interface
static void sl_destroy_host_data_device_manager(struct wl_resource* resource) {
struct sl_host_data_device_manager* host =
wl_resource_get_user_data(resource);
static_cast<sl_host_data_device_manager*>(
wl_resource_get_user_data(resource));
wl_data_device_manager_destroy(host->proxy);
wl_resource_set_user_data(resource, NULL);
free(host);
delete host;
}
static void sl_bind_host_data_device_manager(struct wl_client* client,
@ -556,10 +534,7 @@ static void sl_bind_host_data_device_manager(struct wl_client* client,
uint32_t version,
uint32_t id) {
struct sl_context* ctx = (struct sl_context*)data;
struct sl_host_data_device_manager* host;
host = malloc(sizeof(*host));
assert(host);
struct sl_host_data_device_manager* host = new sl_host_data_device_manager();
host->ctx = ctx;
host->resource =
wl_resource_create(client, &wl_data_device_manager_interface,
@ -567,9 +542,9 @@ static void sl_bind_host_data_device_manager(struct wl_client* client,
wl_resource_set_implementation(host->resource,
&sl_data_device_manager_implementation, host,
sl_destroy_host_data_device_manager);
host->proxy = wl_registry_bind(
host->proxy = static_cast<wl_data_device_manager*>(wl_registry_bind(
wl_display_get_registry(ctx->display), ctx->data_device_manager->id,
&wl_data_device_manager_interface, ctx->data_device_manager->version);
&wl_data_device_manager_interface, ctx->data_device_manager->version));
wl_data_device_manager_set_user_data(host->proxy, host);
}

View File

@ -1,8 +1,9 @@
// Copyright 2018 The Chromium OS Authors. All rights reserved.
// Copyright 2018 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "sommelier.h"
#include "sommelier.h" // NOLINT(build/include_directory)
#include "sommelier-tracing.h" // NOLINT(build/include_directory)
#include <assert.h>
#include <stdlib.h>
@ -15,7 +16,9 @@ static void sl_registry_bind(struct wl_client* client,
const char* interface,
uint32_t version,
uint32_t id) {
struct sl_host_registry* host = wl_resource_get_user_data(resource);
TRACE_EVENT("display", "sl_registry_bind");
struct sl_host_registry* host =
static_cast<sl_host_registry*>(wl_resource_get_user_data(resource));
struct sl_global* global;
wl_list_for_each(global, &host->ctx->globals, link) {
@ -23,6 +26,7 @@ static void sl_registry_bind(struct wl_client* client,
break;
}
assert(sl_client_supports_interface(host->ctx, client, global->interface));
assert(&global->link != &host->ctx->globals);
assert(version != 0);
assert(global->version >= version);
@ -36,7 +40,9 @@ static const struct wl_registry_interface sl_registry_implementation = {
static void sl_sync_callback_done(void* data,
struct wl_callback* callback,
uint32_t serial) {
struct sl_host_callback* host = wl_callback_get_user_data(callback);
TRACE_EVENT("display", "sl_sync_callback_done");
struct sl_host_callback* host =
static_cast<sl_host_callback*>(wl_callback_get_user_data(callback));
wl_callback_send_done(host->resource, serial);
wl_resource_destroy(host->resource);
@ -46,48 +52,46 @@ static const struct wl_callback_listener sl_sync_callback_listener = {
sl_sync_callback_done};
static void sl_host_callback_destroy(struct wl_resource* resource) {
struct sl_host_callback* host = wl_resource_get_user_data(resource);
struct sl_host_callback* host =
static_cast<sl_host_callback*>(wl_resource_get_user_data(resource));
wl_callback_destroy(host->proxy);
wl_resource_set_user_data(resource, NULL);
free(host);
delete host;
}
static void sl_display_sync(struct wl_client* client,
struct wl_resource* resource,
uint32_t id) {
struct sl_context* ctx = wl_resource_get_user_data(resource);
struct sl_host_callback* host_callback;
host_callback = malloc(sizeof(*host_callback));
assert(host_callback);
struct sl_context* ctx =
static_cast<sl_context*>(wl_resource_get_user_data(resource));
struct sl_host_callback* host_callback = new sl_host_callback();
host_callback->resource =
wl_resource_create(client, &wl_callback_interface, 1, id);
wl_resource_set_implementation(host_callback->resource, NULL, host_callback,
sl_host_callback_destroy);
host_callback->proxy = wl_display_sync(ctx->display);
wl_callback_set_user_data(host_callback->proxy, host_callback);
wl_callback_add_listener(host_callback->proxy, &sl_sync_callback_listener,
host_callback);
}
static void sl_destroy_host_registry(struct wl_resource* resource) {
struct sl_host_registry* host = wl_resource_get_user_data(resource);
struct sl_host_registry* host =
static_cast<sl_host_registry*>(wl_resource_get_user_data(resource));
wl_list_remove(&host->link);
free(host);
delete host;
}
static void sl_display_get_registry(struct wl_client* client,
struct wl_resource* resource,
uint32_t id) {
struct sl_context* ctx = wl_resource_get_user_data(resource);
struct sl_host_registry* host_registry;
struct sl_context* ctx =
static_cast<sl_context*>(wl_resource_get_user_data(resource));
struct sl_global* global;
host_registry = malloc(sizeof(*host_registry));
assert(host_registry);
struct sl_host_registry* host_registry = new sl_host_registry();
host_registry->ctx = ctx;
host_registry->resource =
@ -98,10 +102,12 @@ static void sl_display_get_registry(struct wl_client* client,
sl_destroy_host_registry);
wl_list_for_each(global, &ctx->globals, link) {
if (sl_client_supports_interface(ctx, client, global->interface)) {
wl_resource_post_event(host_registry->resource, WL_REGISTRY_GLOBAL,
global->name, global->interface->name,
global->version);
}
}
}
static const struct wl_display_interface sl_display_implementation = {
@ -120,7 +126,8 @@ static enum wl_iterator_result sl_set_implementation(
return WL_ITERATOR_CONTINUE;
}
void sl_set_display_implementation(struct sl_context* ctx) {
void sl_set_display_implementation(struct sl_context* ctx,
struct wl_client* client) {
// Find display resource and set implementation.
wl_client_for_each_resource(ctx->client, sl_set_implementation, ctx);
wl_client_for_each_resource(client, sl_set_implementation, ctx);
}

View File

@ -0,0 +1,312 @@
// Copyright 2020 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "sommelier.h" // NOLINT(build/include_directory)
#include "sommelier-tracing.h" // NOLINT(build/include_directory)
#include <assert.h>
#include <errno.h>
#include <libevdev/libevdev.h>
#include <libevdev/libevdev-uinput.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "gaming-input-unstable-v2-client-protocol.h" // NOLINT(build/include_directory)
// Overview of state management via gaming events, in order:
// 1) Acquire gaming seats (in sommelier.cc)
// 2) Add listeners to gaming seats
// 3) Listen for zcr_gaming_seat_v2.gamepad_added to construct a 'default'
// game controller (not currently implemented)
// Calls libevdev_new, libevdev_enable_event_type,
// libevdev_uinput_create_from_device
// 4) Listen for zcr_gaming_seat_v2.gamepad_added_with_device_info to construct
// a custom game controller
// Calls libevdev_new
// 5) Listen for zcr_gamepad_v2.axis_added to fill in a custom game controller
// Calls libevdev_enable_event_type
// 6) Listen for zcr_gamepad_v2.activated to finalize a custom game controller
// Calls libevdev_uinput_create_from_device
// 7) Listen for zcr_gamepad_v2.axis to set frame state for game controller
// Calls libevdev_uinput_write_event
// 8) Listen for zcr_gamepad_v2.button to set frame state for game controller
// Calls libevdev_uinput_write_event
// 9) Listen for zcr_gamepad_v2.frame to emit collected frame
// Calls libevdev_uinput_write_event(EV_SYN)
// 10) Listen for zcr_gamepad_v2.removed to destroy gamepad
// Must handle gamepads in all states of construction or error
enum GamepadActivationState {
kStateUnknown = 0, // Should not happen
kStatePending = 1, // Constructed, pending axis definition
kStateActivated = 2, // Fully activated
kStateError = 3 // Error occurred during construction; ignore gracefully
};
const char kXboxName[] = "Microsoft X-Box One S pad";
const uint32_t kUsbBus = 0x03;
const uint32_t kXboxVendor = 0x45e;
const uint32_t kXboxProduct = 0x2ea;
const uint32_t kXboxVersion = 0x301;
// Note: the BT vendor ID for SteelSeries is due to a chipset bug
// and is not an actual claimed Vendor ID.
const uint32_t kSteelSeriesVendorBt = 0x111;
const uint32_t kSteelSeriesProductStratusDuoBt = 0x1431;
const uint32_t kSteelSeriesProductStratusPlusBt = 0x1434;
const uint32_t kStadiaVendor = 0x18d1;
const uint32_t kStadiaProduct = 0x9400;
const uint32_t kStadiaVersion = 0x111;
// Note: the majority of protocol errors are treated as non-fatal, and
// are intended to be handled gracefully, as is removal at any
// state of construction or operation. We should expect that
// 'sudden removal' can happen at any time, due to hotplugging
// or unexpected state changes from the wayland server.
static void sl_internal_gamepad_removed(void* data,
struct zcr_gamepad_v2* gamepad) {
TRACE_EVENT("gaming", "sl_internal_gamepad_removed");
struct sl_host_gamepad* host_gamepad = (struct sl_host_gamepad*)data;
assert(host_gamepad->state == kStatePending ||
host_gamepad->state == kStateActivated ||
host_gamepad->state == kStateError);
if (host_gamepad->uinput_dev != NULL)
libevdev_uinput_destroy(host_gamepad->uinput_dev);
if (host_gamepad->ev_dev != NULL)
libevdev_free(host_gamepad->ev_dev);
zcr_gamepad_v2_destroy(gamepad);
wl_list_remove(&host_gamepad->link);
delete host_gamepad;
}
static uint32_t remap_axis(struct sl_host_gamepad* host_gamepad,
uint32_t axis) {
if (host_gamepad->axes_quirk) {
if (axis == ABS_Z)
axis = ABS_RX;
else if (axis == ABS_RZ)
axis = ABS_RY;
else if (axis == ABS_BRAKE)
axis = ABS_Z;
else if (axis == ABS_GAS)
axis = ABS_RZ;
}
return axis;
}
static void sl_internal_gamepad_axis(void* data,
struct zcr_gamepad_v2* gamepad,
uint32_t time,
uint32_t axis,
wl_fixed_t value) {
TRACE_EVENT("gaming", "sl_internal_gamepad_axis");
struct sl_host_gamepad* host_gamepad = (struct sl_host_gamepad*)data;
if (host_gamepad->state != kStateActivated)
return;
axis = remap_axis(host_gamepad, axis);
// Note: incoming time is ignored, it will be regenerated from current time.
libevdev_uinput_write_event(host_gamepad->uinput_dev, EV_ABS, axis,
wl_fixed_to_double(value));
}
static void sl_internal_gamepad_button(void* data,
struct zcr_gamepad_v2* gamepad,
uint32_t time,
uint32_t button,
uint32_t state,
wl_fixed_t analog) {
TRACE_EVENT("gaming", "sl_internal_gamepad_button");
struct sl_host_gamepad* host_gamepad = (struct sl_host_gamepad*)data;
if (host_gamepad->state != kStateActivated)
return;
// Note: Exo wayland server always sends analog==0, only pay attention
// to state.
int value = (state == ZCR_GAMEPAD_V2_BUTTON_STATE_PRESSED) ? 1 : 0;
// Note: incoming time is ignored, it will be regenerated from current time.
libevdev_uinput_write_event(host_gamepad->uinput_dev, EV_KEY, button, value);
}
static void sl_internal_gamepad_frame(void* data,
struct zcr_gamepad_v2* gamepad,
uint32_t time) {
TRACE_EVENT("gaming", "sl_internal_gamepad_frame");
struct sl_host_gamepad* host_gamepad = (struct sl_host_gamepad*)data;
if (host_gamepad->state != kStateActivated)
return;
// Note: incoming time is ignored, it will be regenerated from current time.
libevdev_uinput_write_event(host_gamepad->uinput_dev, EV_SYN, SYN_REPORT, 0);
}
static void sl_internal_gamepad_axis_added(void* data,
struct zcr_gamepad_v2* gamepad,
uint32_t index,
int32_t min_value,
int32_t max_value,
int32_t flat,
int32_t fuzz,
int32_t resolution) {
TRACE_EVENT("gaming", "sl_internal_gamepad_axis_added");
struct sl_host_gamepad* host_gamepad = (struct sl_host_gamepad*)data;
struct input_absinfo info = {.value = 0, // Does this matter?
.minimum = min_value,
.maximum = max_value,
.fuzz = fuzz,
.flat = flat,
.resolution = resolution};
if (host_gamepad->state != kStatePending) {
fprintf(stderr, "error: %s invoked in unexpected state %d\n", __func__,
host_gamepad->state);
host_gamepad->state = kStateError;
return;
}
index = remap_axis(host_gamepad, index);
libevdev_enable_event_code(host_gamepad->ev_dev, EV_ABS, index, &info);
}
static void sl_internal_gamepad_activated(void* data,
struct zcr_gamepad_v2* gamepad) {
TRACE_EVENT("gaming", "sl_internal_gamepad_activated");
struct sl_host_gamepad* host_gamepad = (struct sl_host_gamepad*)data;
if (host_gamepad->state != kStatePending) {
fprintf(stderr, "error: %s invoked in unexpected state %d\n", __func__,
host_gamepad->state);
host_gamepad->state = kStateError;
return;
}
int err = libevdev_uinput_create_from_device(host_gamepad->ev_dev,
LIBEVDEV_UINPUT_OPEN_MANAGED,
&host_gamepad->uinput_dev);
if (err == 0) {
// TODO(kenalba): can we destroy and clean up the ev_dev now?
host_gamepad->state = kStateActivated;
} else {
fprintf(stderr,
"error: libevdev_uinput_create_from_device failed with error %d\n",
err);
host_gamepad->state = kStateError;
}
}
static void sl_internal_gamepad_vibrator_added(
void* data,
struct zcr_gamepad_v2* gamepad,
struct zcr_gamepad_vibrator_v2* vibrator) {
TRACE_EVENT("gaming", "sl_internal_gamepad_vibrator_added");
// TODO(kenalba): add vibration logic
}
static const struct zcr_gamepad_v2_listener sl_internal_gamepad_listener = {
sl_internal_gamepad_removed, sl_internal_gamepad_axis,
sl_internal_gamepad_button, sl_internal_gamepad_frame,
sl_internal_gamepad_axis_added, sl_internal_gamepad_activated,
sl_internal_gamepad_vibrator_added};
static void sl_internal_gaming_seat_gamepad_added_with_device_info(
void* data,
struct zcr_gaming_seat_v2* gaming_seat,
struct zcr_gamepad_v2* gamepad,
const char* name,
uint32_t bus,
uint32_t vendor_id,
uint32_t product_id,
uint32_t version) {
TRACE_EVENT("gaming",
"sl_internal_gaming_seat_gamepad_added_with_device_info");
struct sl_context* ctx = (struct sl_context*)data;
struct sl_host_gamepad* host_gamepad = new sl_host_gamepad();
wl_list_insert(&ctx->gamepads, &host_gamepad->link);
zcr_gamepad_v2_add_listener(gamepad, &sl_internal_gamepad_listener,
host_gamepad);
host_gamepad->ctx = ctx;
host_gamepad->state = kStatePending;
host_gamepad->ev_dev = libevdev_new();
host_gamepad->uinput_dev = NULL;
host_gamepad->axes_quirk = false;
if (host_gamepad->ev_dev == NULL) {
fprintf(stderr, "error: libevdev_new failed\n");
host_gamepad->state = kStateError;
return;
}
// We provide limited remapping at this time. Only moderately XBox360
// HID compatible controllers are likely to work well.
if (product_id == kStadiaProduct && vendor_id == kStadiaVendor &&
version == kStadiaVersion) {
host_gamepad->axes_quirk = true;
} else if (bus == ZCR_GAMING_SEAT_V2_BUS_TYPE_BLUETOOTH &&
vendor_id == kSteelSeriesVendorBt &&
(product_id == kSteelSeriesProductStratusDuoBt ||
product_id == kSteelSeriesProductStratusPlusBt)) {
host_gamepad->axes_quirk = true;
}
// Describe a common controller
libevdev_set_name(host_gamepad->ev_dev, kXboxName);
libevdev_set_id_bustype(host_gamepad->ev_dev, kUsbBus);
libevdev_set_id_vendor(host_gamepad->ev_dev, kXboxVendor);
libevdev_set_id_product(host_gamepad->ev_dev, kXboxProduct);
libevdev_set_id_version(host_gamepad->ev_dev, kXboxVersion);
// Enable common set of buttons
// Note: Do not enable BTN_TL2 or BTN_TR2, as they will significantly
// change the Linux joydev interpretation of the triggers on ABS_Z/ABS_RZ.
int buttons[] = {BTN_SOUTH, BTN_EAST, BTN_NORTH, BTN_WEST,
BTN_TL, BTN_TR, BTN_THUMBL, BTN_THUMBR,
BTN_SELECT, BTN_START, BTN_MODE};
for (unsigned int i = 0; i < ARRAY_SIZE(buttons); i++)
libevdev_enable_event_code(host_gamepad->ev_dev, EV_KEY, buttons[i], NULL);
} // NOLINT(whitespace/indent), lint bug b/173143790
// Note: not currently implemented by Exo.
static void sl_internal_gaming_seat_gamepad_added(
void* data,
struct zcr_gaming_seat_v2* gaming_seat,
struct zcr_gamepad_v2* gamepad) {
TRACE_EVENT("gaming", "sl_internal_gaming_seat_gamepad_added");
fprintf(stderr,
"error: sl_internal_gaming_seat_gamepad_added unimplemented\n");
}
static const struct zcr_gaming_seat_v2_listener
sl_internal_gaming_seat_listener = {
sl_internal_gaming_seat_gamepad_added,
sl_internal_gaming_seat_gamepad_added_with_device_info};
void sl_gaming_seat_add_listener(struct sl_context* ctx) {
if (ctx->gaming_input_manager && ctx->gaming_input_manager->internal) {
TRACE_EVENT("gaming", "sl_gaming_seat_add_listener");
// TODO(kenalba): does gaming_seat need to persist in ctx?
struct zcr_gaming_seat_v2* gaming_seat =
zcr_gaming_input_v2_get_gaming_seat(ctx->gaming_input_manager->internal,
ctx->default_seat->proxy);
zcr_gaming_seat_v2_add_listener(gaming_seat,
&sl_internal_gaming_seat_listener, ctx);
}
}

View File

@ -0,0 +1,40 @@
// Copyright 2021 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "sommelier-global.h" // NOLINT(build/include_directory)
#include <assert.h>
#include "sommelier.h" // NOLINT(build/include_directory)
#include "sommelier-tracing.h" // NOLINT(build/include_directory)
struct sl_global* sl_global_create(struct sl_context* ctx,
const struct wl_interface* interface,
int version,
void* data,
wl_global_bind_func_t bind) {
TRACE_EVENT("other", "sl_global_create");
struct sl_host_registry* registry;
assert(version > 0);
assert(version <= interface->version);
struct sl_global* global = static_cast<sl_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;
}

View File

@ -0,0 +1,16 @@
// Copyright 2021 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef VM_TOOLS_SOMMELIER_SOMMELIER_GLOBAL_H_
#define VM_TOOLS_SOMMELIER_SOMMELIER_GLOBAL_H_
#include <wayland-server-core.h>
struct sl_global* sl_global_create(struct sl_context* ctx,
const struct wl_interface* interface,
int version,
void* data,
wl_global_bind_func_t bind);
#endif // VM_TOOLS_SOMMELIER_SOMMELIER_GLOBAL_H_

View File

@ -1,15 +1,19 @@
// Copyright 2018 The Chromium OS Authors. All rights reserved.
// Copyright 2018 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "sommelier.h"
#include "sommelier.h" // NOLINT(build/include_directory)
#include "sommelier-tracing.h" // NOLINT(build/include_directory)
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include "aura-shell-client-protocol.h"
#include "gtk-shell-server-protocol.h"
#include "aura-shell-client-protocol.h" // NOLINT(build/include_directory)
#include "gtk-shell-server-protocol.h" // NOLINT(build/include_directory)
#define NATIVE_WAYLAND_APPLICATION_ID_FORMAT \
"org.chromium.guest_os.%s.wayland.%s"
struct sl_host_gtk_shell {
struct sl_aura_shell* aura_shell;
@ -36,10 +40,15 @@ static void sl_gtk_surface_set_dbus_properties(
const char* window_object_path,
const char* application_object_path,
const char* unique_bus_name) {
struct sl_host_gtk_surface* host = wl_resource_get_user_data(resource);
struct sl_host_gtk_surface* host =
static_cast<sl_host_gtk_surface*>(wl_resource_get_user_data(resource));
zaura_surface_set_application_id(host->proxy, application_id);
}
char* application_id_str =
sl_xasprintf(NATIVE_WAYLAND_APPLICATION_ID_FORMAT,
host->aura_shell->ctx->vm_id, application_id);
zaura_surface_set_application_id(host->proxy, application_id_str);
} // NOLINT(whitespace/indent)
static void sl_gtk_surface_set_modal(struct wl_client* client,
struct wl_resource* resource) {}
@ -56,25 +65,24 @@ static const struct gtk_surface1_interface sl_gtk_surface_implementation = {
sl_gtk_surface_unset_modal, sl_gtk_surface_present};
static void sl_destroy_host_gtk_surface(struct wl_resource* resource) {
struct sl_host_gtk_surface* host = wl_resource_get_user_data(resource);
struct sl_host_gtk_surface* host =
static_cast<sl_host_gtk_surface*>(wl_resource_get_user_data(resource));
zaura_surface_destroy(host->proxy);
wl_list_remove(&host->link);
wl_resource_set_user_data(resource, NULL);
free(host);
delete host;
}
static void sl_gtk_shell_get_gtk_surface(struct wl_client* client,
struct wl_resource* resource,
uint32_t id,
struct wl_resource* surface_resource) {
struct sl_host_gtk_shell* host = wl_resource_get_user_data(resource);
struct sl_host_surface* host_surface =
wl_resource_get_user_data(surface_resource);
struct sl_host_gtk_surface* host_gtk_surface;
host_gtk_surface = malloc(sizeof(*host_gtk_surface));
assert(host_gtk_surface);
struct sl_host_gtk_shell* host =
static_cast<sl_host_gtk_shell*>(wl_resource_get_user_data(resource));
struct sl_host_surface* host_surface = static_cast<sl_host_surface*>(
wl_resource_get_user_data(surface_resource));
struct sl_host_gtk_surface* host_gtk_surface = new sl_host_gtk_surface();
wl_list_insert(&host->surfaces, &host_gtk_surface->link);
host_gtk_surface->aura_shell = host->aura_shell;
@ -88,10 +96,13 @@ static void sl_gtk_shell_get_gtk_surface(struct wl_client* client,
zaura_surface_set_startup_id(host_gtk_surface->proxy, host->startup_id);
}
// TODO(b/244651040): when adding changing the startup id format, also add vm_id
// here.
static void sl_gtk_shell_set_startup_id(struct wl_client* client,
struct wl_resource* resource,
const char* startup_id) {
struct sl_host_gtk_shell* host = wl_resource_get_user_data(resource);
struct sl_host_gtk_shell* host =
static_cast<sl_host_gtk_shell*>(wl_resource_get_user_data(resource));
struct sl_host_gtk_surface* surface;
free(host->startup_id);
@ -110,19 +121,22 @@ static const struct gtk_shell1_interface sl_gtk_shell_implementation = {
sl_gtk_shell_system_bell};
static void sl_destroy_host_gtk_shell(struct wl_resource* resource) {
struct sl_host_gtk_shell* host = wl_resource_get_user_data(resource);
struct sl_host_gtk_shell* host =
static_cast<sl_host_gtk_shell*>(wl_resource_get_user_data(resource));
free(host->startup_id);
wl_callback_destroy(host->callback);
zaura_shell_destroy(host->proxy);
wl_resource_set_user_data(resource, NULL);
free(host);
delete host;
}
static void sl_gtk_shell_callback_done(void* data,
struct wl_callback* callback,
uint32_t serial) {
struct sl_host_gtk_shell* host = wl_callback_get_user_data(callback);
TRACE_EVENT("shell", "sl_gtk_shell_callback_done");
struct sl_host_gtk_shell* host =
static_cast<sl_host_gtk_shell*>(wl_callback_get_user_data(callback));
gtk_shell1_send_capabilities(host->resource, 0);
}
@ -135,23 +149,19 @@ static void sl_bind_host_gtk_shell(struct wl_client* client,
uint32_t version,
uint32_t id) {
struct sl_context* ctx = (struct sl_context*)data;
struct sl_host_gtk_shell* host;
host = malloc(sizeof(*host));
assert(host);
struct sl_host_gtk_shell* host = new sl_host_gtk_shell();
host->aura_shell = ctx->aura_shell;
host->startup_id = NULL;
wl_list_init(&host->surfaces);
host->resource = wl_resource_create(client, &gtk_shell1_interface, 1, id);
wl_resource_set_implementation(host->resource, &sl_gtk_shell_implementation,
host, sl_destroy_host_gtk_shell);
host->proxy = wl_registry_bind(wl_display_get_registry(ctx->display),
ctx->aura_shell->id, &zaura_shell_interface,
ctx->aura_shell->version);
host->proxy = static_cast<zaura_shell*>(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);
}

View File

@ -0,0 +1,9 @@
// Copyright 2021 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
int real_main(int argc, char** argv);
int main(int argc, char** argv) {
return real_main(argc, argv);
}

View File

@ -1,348 +0,0 @@
// Copyright 2018 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "sommelier.h"
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <wayland-client.h>
#include "aura-shell-client-protocol.h"
#define MAX_OUTPUT_SCALE 2
#define INCH_IN_MM 25.4
// Legacy X11 applications use DPI to decide on their scale. This value is what
// the convention for a "normal" scale is. One way to verify the convention is
// to note the DPI of a typical monitor circa ~2005, i.e. 20" 1080p.
#define DEFACTO_DPI 96
double sl_output_aura_scale_factor_to_double(int scale_factor) {
// Aura scale factor is an enum that for all currently know values
// is a scale value multipled by 1000. For example, enum value for
// 1.25 scale factor is 1250.
return scale_factor / 1000.0;
}
int dpi_to_physical_mm(double dpi, int px) {
return px * (INCH_IN_MM / dpi);
}
void sl_output_get_host_output_state(struct sl_host_output* host,
int* scale,
int* physical_width,
int* physical_height,
int* width,
int* height) {
// The user's chosen zoom level.
double current_scale =
sl_output_aura_scale_factor_to_double(host->current_scale);
// The scale applied to a screen at the default zoom. I.e. this value
// determines the meaning of "100%" zoom, and how zoom relates to the
// apparent resolution:
//
// apparent_res = native_res / device_scale_factor * current_scale
//
// e.g.: On a device with a DSF of 2.0, 80% zoom really means "apply 1.6x
// scale", and 50% zoom would give you an apparent resolution equal to the
// native one.
double device_scale_factor =
sl_output_aura_scale_factor_to_double(host->device_scale_factor);
// Optimistically, we will try to apply the scale that the user chose.
// Failing that, we will use the scale set for this wl_output.
double applied_scale = device_scale_factor * current_scale;
if (!host->ctx->aura_shell) {
applied_scale = host->scale_factor;
}
int target_dpi = DEFACTO_DPI;
if (host->ctx->xwayland) {
// For X11, we must fix the scale to be 1 (since X apps typically can't
// handle scaling). As a result, we adjust the resolution (based on the
// scale we want to apply and sommelier's configuration) and the physical
// dimensions (based on what DPI we want the applications to use). E.g.:
// - Device scale is 1.25x, with 1920x1080 resolution on a 295mm by 165mm
// screen.
// - User chosen zoom is 130%
// - Sommelier is scaled to 0.5 (a.k.a low density). Since ctx->scale also
// has the device scale, it will be 0.625 (i.e. 0.5 * 1.25).
// - We want the DPI to be 120 (i.e. 96 * 1.25)
// - Meaning 0.21 mm/px
// - We report resolution 738x415 (1920x1080 * 0.5 / 1.3)
// - We report dimensions 155mm by 87mm (738x415 * 0.21)
// This is mostly expected, another way of thinking about them is that zoom
// and scale modify the application's understanding of length:
// - Increasing the zoom makes lengths appear longer (i.e. fewer mm to work
// with over the same real length).
// - Scaling the screen does the inverse.
if (scale)
*scale = 1;
*width = host->width * host->ctx->scale / applied_scale;
*height = host->height * host->ctx->scale / applied_scale;
target_dpi = DEFACTO_DPI * device_scale_factor;
*physical_width = dpi_to_physical_mm(target_dpi, *width);
*physical_height = dpi_to_physical_mm(target_dpi, *height);
} else {
// For wayland, we directly apply the scale which combines the user's chosen
// preference (from aura) and the scale which this sommelier was configured
// for (i.e. based on ctx->scale, which comes from the env/cmd line).
//
// See above comment: ctx->scale already has the device_scale_factor in it,
// so this maths actually looks like:
//
// applied / ctx->scale
// = (current*DSF) / (config*DSF)
// = current / config
//
// E.g. if we configured sommelier to scale everything 0.5x, and the user
// has chosen 130% zoom, we are applying 2.6x scale factor.
int s = MIN(ceil(applied_scale / host->ctx->scale), MAX_OUTPUT_SCALE);
if (scale)
*scale = s;
*physical_width = host->physical_width;
*physical_height = host->physical_height;
*width = host->width * host->ctx->scale * s / applied_scale;
*height = host->height * host->ctx->scale * s / applied_scale;
target_dpi = (*width * INCH_IN_MM) / *physical_width;
}
if (host->ctx->dpi.size) {
int adjusted_dpi = *((int*)host->ctx->dpi.data);
int* p;
// Choose the DPI bucket which is closest to the target DPI which we
// calculated above.
wl_array_for_each(p, &host->ctx->dpi) {
if (abs(*p - target_dpi) < abs(adjusted_dpi - target_dpi))
adjusted_dpi = *p;
}
*physical_width = dpi_to_physical_mm(adjusted_dpi, *width);
*physical_height = dpi_to_physical_mm(adjusted_dpi, *height);
}
}
void sl_output_send_host_output_state(struct sl_host_output* host) {
int scale;
int physical_width;
int physical_height;
int width;
int height;
sl_output_get_host_output_state(host, &scale, &physical_width,
&physical_height, &width, &height);
// Use density of internal display for all Xwayland outputs. X11 clients
// typically lack support for dynamically changing density so it's
// preferred to always use the density of the internal display.
if (host->ctx->xwayland) {
struct sl_host_output* output;
wl_list_for_each(output, &host->ctx->host_outputs, link) {
if (output->internal) {
int internal_width;
int internal_height;
sl_output_get_host_output_state(output, NULL, &physical_width,
&physical_height, &internal_width,
&internal_height);
physical_width = (physical_width * width) / internal_width;
physical_height = (physical_height * height) / internal_height;
break;
}
}
}
// X/Y are best left at origin as managed X windows are kept centered on
// the root window. The result is that all outputs are overlapping and
// pointer events can always be dispatched to the visible region of the
// window.
wl_output_send_geometry(host->resource, 0, 0, physical_width, physical_height,
host->subpixel, host->make, host->model,
host->transform);
wl_output_send_mode(host->resource, host->flags | WL_OUTPUT_MODE_CURRENT,
width, height, host->refresh);
if (wl_resource_get_version(host->resource) >= WL_OUTPUT_SCALE_SINCE_VERSION)
wl_output_send_scale(host->resource, scale);
if (wl_resource_get_version(host->resource) >= WL_OUTPUT_DONE_SINCE_VERSION)
wl_output_send_done(host->resource);
}
static void sl_output_geometry(void* data,
struct wl_output* output,
int x,
int y,
int physical_width,
int physical_height,
int subpixel,
const char* make,
const char* model,
int transform) {
struct sl_host_output* host = wl_output_get_user_data(output);
host->x = x;
host->y = y;
host->physical_width = physical_width;
host->physical_height = physical_height;
host->subpixel = subpixel;
free(host->model);
host->model = strdup(model);
free(host->make);
host->make = strdup(make);
host->transform = transform;
}
static void sl_output_mode(void* data,
struct wl_output* output,
uint32_t flags,
int width,
int height,
int refresh) {
struct sl_host_output* host = wl_output_get_user_data(output);
host->flags = flags;
host->width = width;
host->height = height;
host->refresh = refresh;
}
static void sl_output_done(void* data, struct wl_output* output) {
struct sl_host_output* host = wl_output_get_user_data(output);
// Early out if scale is expected but not yet know.
if (host->expecting_scale)
return;
sl_output_send_host_output_state(host);
// Expect scale if aura output exists.
if (host->aura_output)
host->expecting_scale = 1;
}
static void sl_output_scale(void* data,
struct wl_output* output,
int32_t scale_factor) {
struct sl_host_output* host = wl_output_get_user_data(output);
host->scale_factor = scale_factor;
}
static const struct wl_output_listener sl_output_listener = {
sl_output_geometry, sl_output_mode, sl_output_done, sl_output_scale};
static void sl_aura_output_scale(void* data,
struct zaura_output* output,
uint32_t flags,
uint32_t scale) {
struct sl_host_output* host = zaura_output_get_user_data(output);
if (flags & ZAURA_OUTPUT_SCALE_PROPERTY_CURRENT)
host->current_scale = scale;
if (flags & ZAURA_OUTPUT_SCALE_PROPERTY_PREFERRED)
host->preferred_scale = scale;
host->expecting_scale = 0;
}
static void sl_aura_output_connection(void* data,
struct zaura_output* output,
uint32_t connection) {
struct sl_host_output* host = zaura_output_get_user_data(output);
host->internal = connection == ZAURA_OUTPUT_CONNECTION_TYPE_INTERNAL;
}
static void sl_aura_output_device_scale_factor(void* data,
struct zaura_output* output,
uint32_t device_scale_factor) {
struct sl_host_output* host = zaura_output_get_user_data(output);
host->device_scale_factor = device_scale_factor;
}
static const struct zaura_output_listener sl_aura_output_listener = {
sl_aura_output_scale, sl_aura_output_connection,
sl_aura_output_device_scale_factor};
static void sl_destroy_host_output(struct wl_resource* resource) {
struct sl_host_output* host = wl_resource_get_user_data(resource);
if (host->aura_output)
zaura_output_destroy(host->aura_output);
if (wl_output_get_version(host->proxy) >= WL_OUTPUT_RELEASE_SINCE_VERSION) {
wl_output_release(host->proxy);
} else {
wl_output_destroy(host->proxy);
}
wl_resource_set_user_data(resource, NULL);
wl_list_remove(&host->link);
free(host->make);
free(host->model);
free(host);
}
static void sl_bind_host_output(struct wl_client* client,
void* data,
uint32_t version,
uint32_t id) {
struct sl_output* output = (struct sl_output*)data;
struct sl_context* ctx = output->ctx;
struct sl_host_output* host;
host = malloc(sizeof(*host));
assert(host);
host->ctx = ctx;
host->resource = wl_resource_create(client, &wl_output_interface,
MIN(version, output->version), id);
wl_resource_set_implementation(host->resource, NULL, host,
sl_destroy_host_output);
host->proxy = wl_registry_bind(wl_display_get_registry(ctx->display),
output->id, &wl_output_interface,
wl_resource_get_version(host->resource));
wl_output_set_user_data(host->proxy, host);
wl_output_add_listener(host->proxy, &sl_output_listener, host);
host->aura_output = NULL;
// We assume that first output is internal by default.
host->internal = wl_list_empty(&ctx->host_outputs);
host->x = 0;
host->y = 0;
host->physical_width = 0;
host->physical_height = 0;
host->subpixel = WL_OUTPUT_SUBPIXEL_UNKNOWN;
host->make = strdup("unknown");
host->model = strdup("unknown");
host->transform = WL_OUTPUT_TRANSFORM_NORMAL;
host->flags = 0;
host->width = 1024;
host->height = 768;
host->refresh = 60000;
host->scale_factor = 1;
host->current_scale = 1000;
host->preferred_scale = 1000;
host->device_scale_factor = 1000;
host->expecting_scale = 0;
wl_list_insert(ctx->host_outputs.prev, &host->link);
if (ctx->aura_shell) {
host->expecting_scale = 1;
host->internal = 0;
host->aura_output =
zaura_shell_get_aura_output(ctx->aura_shell->internal, host->proxy);
zaura_output_set_user_data(host->aura_output, host);
zaura_output_add_listener(host->aura_output, &sl_aura_output_listener,
host);
}
}
struct sl_global* sl_output_global_create(struct sl_output* output) {
return sl_global_create(output->ctx, &wl_output_interface, output->version,
output, sl_bind_host_output);
}

View File

@ -0,0 +1,607 @@
// Copyright 2022 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "sommelier.h" // NOLINT(build/include_directory)
#include "sommelier-transform.h" // NOLINT(build/include_directory)
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <wayland-client.h>
#include "aura-shell-client-protocol.h" // NOLINT(build/include_directory)
#include "xdg-output-unstable-v1-client-protocol.h" // NOLINT(build/include_directory)
#define MAX_OUTPUT_SCALE 2
#define INCH_IN_MM 25.4
// Legacy X11 applications use DPI to decide on their scale. This value is what
// the convention for a "normal" scale is. One way to verify the convention is
// to note the DPI of a typical monitor circa ~2005, i.e. 20" 1080p.
#define DEFACTO_DPI 96
double sl_output_aura_scale_factor_to_double(int scale_factor) {
// Aura scale factor is an enum that for all currently know values
// is a scale value multipled by 1000. For example, enum value for
// 1.25 scale factor is 1250.
return scale_factor / 1000.0;
}
int dpi_to_physical_mm(double dpi, int px) {
return px * (INCH_IN_MM / dpi);
}
void sl_output_get_host_output_state(struct sl_host_output* host,
int* scale,
int* physical_width,
int* physical_height,
int* width,
int* height) {
// The user's chosen zoom level.
double current_scale =
sl_output_aura_scale_factor_to_double(host->current_scale);
// The scale applied to a screen at the default zoom. I.e. this value
// determines the meaning of "100%" zoom, and how zoom relates to the
// apparent resolution:
//
// apparent_res = native_res / device_scale_factor * current_scale
//
// e.g.: On a device with a DSF of 2.0, 80% zoom really means "apply 1.6x
// scale", and 50% zoom would give you an apparent resolution equal to the
// native one.
double device_scale_factor =
sl_output_aura_scale_factor_to_double(host->device_scale_factor);
// Optimistically, we will try to apply the scale that the user chose.
// Failing that, we will use the scale set for this wl_output.
double applied_scale = device_scale_factor * current_scale;
if (!host->ctx->aura_shell) {
applied_scale = host->scale_factor;
}
int target_dpi = DEFACTO_DPI;
if (host->ctx->xwayland) {
// For X11, we must fix the scale to be 1 (since X apps typically can't
// handle scaling). As a result, we adjust the resolution (based on the
// scale we want to apply and sommelier's configuration) and the physical
// dimensions (based on what DPI we want the applications to use). E.g.:
// - Device scale is 1.25x, with 1920x1080 resolution on a 295mm by 165mm
// screen.
// - User chosen zoom is 130%
// - Sommelier is scaled to 0.5 (a.k.a low density). Since ctx->scale also
// has the device scale, it will be 0.625 (i.e. 0.5 * 1.25).
// - We want the DPI to be 120 (i.e. 96 * 1.25)
// - Meaning 0.21 mm/px
// - We report resolution 738x415 (1920x1080 * 0.5 / 1.3)
// - We report dimensions 155mm by 87mm (738x415 * 0.21)
// This is mostly expected, another way of thinking about them is that zoom
// and scale modify the application's understanding of length:
// - Increasing the zoom makes lengths appear longer (i.e. fewer mm to work
// with over the same real length).
// - Scaling the screen does the inverse.
if (scale)
*scale = 1;
*width = host->width * host->ctx->scale / applied_scale;
*height = host->height * host->ctx->scale / applied_scale;
target_dpi = DEFACTO_DPI * device_scale_factor;
*physical_width = dpi_to_physical_mm(target_dpi, *width);
*physical_height = dpi_to_physical_mm(target_dpi, *height);
} else {
// For wayland, we directly apply the scale which combines the user's chosen
// preference (from aura) and the scale which this sommelier was configured
// for (i.e. based on ctx->scale, which comes from the env/cmd line).
//
// See above comment: ctx->scale already has the device_scale_factor in it,
// so this maths actually looks like:
//
// applied / ctx->scale
// = (current*DSF) / (config*DSF)
// = current / config
//
// E.g. if we configured sommelier to scale everything 0.5x, and the user
// has chosen 130% zoom, we are applying 2.6x scale factor.
int s = MIN(ceil(applied_scale / host->ctx->scale), MAX_OUTPUT_SCALE);
if (scale)
*scale = s;
*physical_width = host->physical_width;
*physical_height = host->physical_height;
*width = host->width * host->ctx->scale * s / applied_scale;
*height = host->height * host->ctx->scale * s / applied_scale;
target_dpi = (*width * INCH_IN_MM) / *physical_width;
}
if (host->ctx->dpi.size) {
int adjusted_dpi = *(reinterpret_cast<int*>(host->ctx->dpi.data));
// Choose the DPI bucket which is closest to the target DPI which we
// calculated above.
int* dpi;
sl_array_for_each(dpi, &host->ctx->dpi) {
if (abs(*dpi - target_dpi) < abs(adjusted_dpi - target_dpi))
adjusted_dpi = *dpi;
}
*physical_width = dpi_to_physical_mm(adjusted_dpi, *width);
*physical_height = dpi_to_physical_mm(adjusted_dpi, *height);
}
}
void sl_output_get_logical_dimensions(struct sl_host_output* host,
bool rotated,
int32_t* width,
int32_t* height) {
if (rotated) {
// Pass the dimensions as is (it could be rotated)
*width = host->logical_width;
*height = host->logical_height;
} else {
// The transform here indicates how a window image will be
// rotated when composited. The incoming surface from the
// application will NOT have its dimensions rotated.
// For this reason, in order to calculate the scale factors
// for direct scale, we will need the non rotated logical
// dimensions.
switch (host->transform) {
case WL_OUTPUT_TRANSFORM_NORMAL:
case WL_OUTPUT_TRANSFORM_180:
case WL_OUTPUT_TRANSFORM_FLIPPED:
case WL_OUTPUT_TRANSFORM_FLIPPED_180:
*width = host->logical_width;
*height = host->logical_height;
break;
default:
*width = host->logical_height;
*height = host->logical_width;
break;
}
}
}
void sl_output_init_dimensions_direct(struct sl_host_output* host,
int* out_scale,
int* out_physical_width,
int* out_physical_height,
int* out_width,
int* out_height) {
int32_t virtual_width = host->width;
int32_t virtual_height = host->height;
// This requires xdg_output_manager, it is assumed that it will be
// available and we will have an appropriate set of logical dimensions
// for this particular output.
assert(host->ctx->viewporter);
assert(host->ctx->xdg_output_manager);
// The virtual width/height is computed by this function here based
// on the physical width/height
sl_transform_output_dimensions(host->ctx, &virtual_width, &virtual_height);
host->virt_scale_x = static_cast<double>(virtual_width) / host->width;
host->virt_scale_y = static_cast<double>(virtual_height) / host->height;
*out_width = virtual_width;
*out_height = virtual_height;
// Force the scale to 1
//
// This is reported to the guest through the wl_output protocol.
// This value will signal by how much a compositor will upscale
// all buffers by (1 is no scale).
*out_scale = 1;
// The physical dimensions (in mm) are the same, regardless
// of the provided scale factor.
*out_physical_width = host->physical_width;
*out_physical_height = host->physical_height;
// Retrieve the logical dimensions
int32_t logical_width, logical_height;
sl_output_get_logical_dimensions(host, /*rotated=*/false, &logical_width,
&logical_height);
// We want to be able to transform from virtual to XDG logical
// coordinates
// Virt to XDG -> div
// XDG to Virt -> mul
host->xdg_scale_x =
static_cast<double>(virtual_width) / static_cast<double>(logical_width);
host->xdg_scale_y =
static_cast<double>(virtual_height) / static_cast<double>(logical_height);
if (host->internal) {
host->ctx->virt_scale_x = host->virt_scale_x;
host->ctx->virt_scale_y = host->virt_scale_y;
host->ctx->xdg_scale_x = host->xdg_scale_x;
host->ctx->xdg_scale_y = host->xdg_scale_y;
}
}
void sl_output_get_dimensions_original(struct sl_host_output* host,
int* out_scale,
int* out_physical_width,
int* out_physical_height,
int* out_width,
int* out_height) {
int scale;
int physical_width;
int physical_height;
int width;
int height;
sl_output_get_host_output_state(host, &scale, &physical_width,
&physical_height, &width, &height);
// Use density of internal display for all Xwayland outputs. X11 clients
// typically lack support for dynamically changing density so it's
// preferred to always use the density of the internal display.
if (host->ctx->xwayland) {
struct sl_host_output* output;
wl_list_for_each(output, &host->ctx->host_outputs, link) {
if (output->internal) {
int internal_width;
int internal_height;
sl_output_get_host_output_state(output, NULL, &physical_width,
&physical_height, &internal_width,
&internal_height);
physical_width = (physical_width * width) / internal_width;
physical_height = (physical_height * height) / internal_height;
break;
}
}
}
*out_scale = scale;
*out_physical_width = physical_width;
*out_physical_height = physical_height;
*out_width = width;
*out_height = height;
}
// Recalculates the virt_x coordinates of outputs when an output is
// add/removed/changed. skip_host is false if the host is being removed
// (as it should not exist in the list already) and true if it's being
// added/changed.
void sl_output_shift_output_x(struct sl_host_output* host, bool skip_host) {
// Outputs are positioned in a line from left to right ordered base on its
// x position.
struct sl_host_output* output;
int next_output_x = 0;
wl_list_for_each(output, &host->ctx->host_outputs, link) {
if (output->virt_x != next_output_x) {
output->virt_x = next_output_x;
// Skipping sending current output's details here if skip host is set
// as they are sent after this method.
if (!skip_host || output != host) {
// scaled_physical_width/height may have not been set yet.
if (!output->scaled_physical_width || !output->scaled_physical_height) {
int scale;
int physical_width;
int physical_height;
int width;
int height;
if (host->ctx->use_direct_scale) {
sl_output_init_dimensions_direct(host, &scale, &physical_width,
&physical_height, &width, &height);
} else {
sl_output_get_dimensions_original(host, &scale, &physical_width,
&physical_height, &width,
&height);
}
host->scaled_physical_width = physical_width;
host->scaled_physical_height = physical_height;
}
wl_output_send_geometry(
output->resource, output->virt_x, output->virt_y,
output->scaled_physical_width, output->scaled_physical_height,
output->subpixel, output->make, output->model, output->transform);
if (wl_resource_get_version(output->resource) >=
WL_OUTPUT_DONE_SINCE_VERSION)
wl_output_send_done(output->resource);
}
}
next_output_x += output->width;
}
}
void sl_output_send_host_output_state(struct sl_host_output* host) {
int scale;
int physical_width;
int physical_height;
int width;
int height;
if (host->ctx->use_direct_scale) {
sl_output_init_dimensions_direct(host, &scale, &physical_width,
&physical_height, &width, &height);
} else {
sl_output_get_dimensions_original(host, &scale, &physical_width,
&physical_height, &width, &height);
}
host->scaled_physical_width = physical_width;
host->scaled_physical_height = physical_height;
// Shift all outputs that are to the right of host to the right if needed.
sl_output_shift_output_x(host, true);
host->virt_y = 0;
wl_output_send_geometry(host->resource, host->virt_x, host->virt_y,
host->scaled_physical_width,
host->scaled_physical_height, host->subpixel,
host->make, host->model, host->transform);
wl_output_send_mode(host->resource, host->flags | WL_OUTPUT_MODE_CURRENT,
width, height, host->refresh);
if (wl_resource_get_version(host->resource) >= WL_OUTPUT_SCALE_SINCE_VERSION)
wl_output_send_scale(host->resource, scale);
if (wl_resource_get_version(host->resource) >= WL_OUTPUT_DONE_SINCE_VERSION)
wl_output_send_done(host->resource);
}
static void sl_output_geometry(void* data,
struct wl_output* output,
int x,
int y,
int physical_width,
int physical_height,
int subpixel,
const char* make,
const char* model,
int transform) {
struct sl_host_output* host =
static_cast<sl_host_output*>(wl_output_get_user_data(output));
host->x = x;
host->y = y;
host->physical_width = physical_width;
host->physical_height = physical_height;
host->subpixel = subpixel;
free(host->model);
host->model = strdup(model);
free(host->make);
host->make = strdup(make);
host->transform = transform;
// host_outputs is sorted by x. Delete then re-insert at the correct position.
wl_list_remove(&host->link);
// Insert at the end by default. If insert_at is not set in the loop,
// hosts's x is larger than all the ones in the list currently.
struct wl_list* insert_at = host->ctx->host_outputs.prev;
struct sl_host_output* iter;
wl_list_for_each(iter, &host->ctx->host_outputs, link) {
if (host->x < iter->x) {
// This is the first output whose x cooridinate is to the right of host,
// therefore insert to the left of it.
insert_at = iter->link.prev;
break;
}
}
wl_list_insert(insert_at, &host->link);
}
static void sl_output_mode(void* data,
struct wl_output* output,
uint32_t flags,
int width,
int height,
int refresh) {
struct sl_host_output* host =
static_cast<sl_host_output*>(wl_output_get_user_data(output));
host->flags = flags;
host->width = width;
host->height = height;
host->refresh = refresh;
}
static void sl_output_done(void* data, struct wl_output* output) {
struct sl_host_output* host =
static_cast<sl_host_output*>(wl_output_get_user_data(output));
// Early out if scale is expected but not yet know.
if (host->expecting_scale)
return;
sl_output_send_host_output_state(host);
// Expect scale if aura output exists.
if (host->aura_output)
host->expecting_scale = 1;
}
static void sl_output_scale(void* data,
struct wl_output* output,
int32_t scale_factor) {
struct sl_host_output* host =
static_cast<sl_host_output*>(wl_output_get_user_data(output));
host->scale_factor = scale_factor;
}
static const struct wl_output_listener sl_output_listener = {
sl_output_geometry, sl_output_mode, sl_output_done, sl_output_scale};
static void sl_aura_output_scale(void* data,
struct zaura_output* output,
uint32_t flags,
uint32_t scale) {
struct sl_host_output* host =
static_cast<sl_host_output*>(zaura_output_get_user_data(output));
if (flags & ZAURA_OUTPUT_SCALE_PROPERTY_CURRENT)
host->current_scale = scale;
if (flags & ZAURA_OUTPUT_SCALE_PROPERTY_PREFERRED)
host->preferred_scale = scale;
host->expecting_scale = 0;
}
static void sl_aura_output_connection(void* data,
struct zaura_output* output,
uint32_t connection) {
struct sl_host_output* host =
static_cast<sl_host_output*>(zaura_output_get_user_data(output));
host->internal = connection == ZAURA_OUTPUT_CONNECTION_TYPE_INTERNAL;
}
static void sl_aura_output_device_scale_factor(void* data,
struct zaura_output* output,
uint32_t device_scale_factor) {
struct sl_host_output* host =
static_cast<sl_host_output*>(zaura_output_get_user_data(output));
host->device_scale_factor = device_scale_factor;
}
static void sl_aura_output_insets(void* data,
struct zaura_output* output,
int top,
int left,
int bottom,
int right) {}
static void sl_aura_output_logical_transform(void* data,
struct zaura_output* output,
int transform) {}
static const struct zaura_output_listener sl_aura_output_listener = {
sl_aura_output_scale, sl_aura_output_connection,
sl_aura_output_device_scale_factor, sl_aura_output_insets,
sl_aura_output_logical_transform};
static void sl_destroy_host_output(struct wl_resource* resource) {
struct sl_host_output* host =
static_cast<sl_host_output*>(wl_resource_get_user_data(resource));
if (host->aura_output)
zaura_output_destroy(host->aura_output);
if (wl_output_get_version(host->proxy) >= WL_OUTPUT_RELEASE_SINCE_VERSION) {
wl_output_release(host->proxy);
} else {
wl_output_destroy(host->proxy);
}
wl_resource_set_user_data(resource, NULL);
wl_list_remove(&host->link);
free(host->make);
free(host->model);
// Shift all outputs to the right of the deleted output to the left.
sl_output_shift_output_x(host, false);
delete host;
}
static void sl_xdg_output_logical_position(
void* data, struct zxdg_output_v1* zxdg_output_v1, int32_t x, int32_t y) {
struct sl_host_output* host = static_cast<sl_host_output*>(
zxdg_output_v1_get_user_data(zxdg_output_v1));
host->logical_y = y;
host->logical_x = x;
}
static void sl_xdg_output_logical_size(void* data,
struct zxdg_output_v1* zxdg_output_v1,
int32_t width,
int32_t height) {
struct sl_host_output* host = static_cast<sl_host_output*>(
zxdg_output_v1_get_user_data(zxdg_output_v1));
host->logical_width = width;
host->logical_height = height;
host->expecting_logical_size = false;
}
static void sl_xdg_output_done(void* data,
struct zxdg_output_v1* zxdg_output_v1) {}
static void sl_xdg_output_name(void* data,
struct zxdg_output_v1* zxdg_output_v1,
const char* name) {}
static void sl_xdg_output_desc(void* data,
struct zxdg_output_v1* zxdg_output_v1,
const char* desc) {}
static const struct zxdg_output_v1_listener sl_xdg_output_listener = {
sl_xdg_output_logical_position, sl_xdg_output_logical_size,
sl_xdg_output_done, sl_xdg_output_name, sl_xdg_output_desc};
static void sl_bind_host_output(struct wl_client* client,
void* data,
uint32_t version,
uint32_t id) {
struct sl_output* output = (struct sl_output*)data;
struct sl_context* ctx = output->ctx;
struct sl_host_output* host = new sl_host_output();
host->ctx = ctx;
host->resource = wl_resource_create(client, &wl_output_interface,
MIN(version, output->version), id);
wl_resource_set_implementation(host->resource, NULL, host,
sl_destroy_host_output);
host->proxy = static_cast<wl_output*>(wl_registry_bind(
wl_display_get_registry(ctx->display), output->id, &wl_output_interface,
wl_resource_get_version(host->resource)));
wl_output_add_listener(host->proxy, &sl_output_listener, host);
output->host_output = host;
host->aura_output = NULL;
// We assume that first output is internal by default.
host->internal = wl_list_empty(&ctx->host_outputs);
host->x = 0;
host->y = 0;
host->virt_x = 0;
host->virt_y = 0;
host->logical_x = 0;
host->logical_y = 0;
host->physical_width = 0;
host->physical_height = 0;
host->scaled_physical_width = 0;
host->scaled_physical_height = 0;
host->subpixel = WL_OUTPUT_SUBPIXEL_UNKNOWN;
host->make = strdup("unknown");
host->model = strdup("unknown");
host->transform = WL_OUTPUT_TRANSFORM_NORMAL;
host->flags = 0;
host->width = 1024;
host->height = 768;
host->logical_width = 1024;
host->logical_height = 768;
host->refresh = 60000;
host->scale_factor = 1;
host->current_scale = 1000;
host->preferred_scale = 1000;
host->device_scale_factor = 1000;
host->expecting_scale = 0;
host->expecting_logical_size = false;
wl_list_insert(ctx->host_outputs.prev, &host->link);
if (ctx->aura_shell) {
host->expecting_scale = 1;
host->internal = 0;
host->aura_output =
zaura_shell_get_aura_output(ctx->aura_shell->internal, host->proxy);
zaura_output_add_listener(host->aura_output, &sl_aura_output_listener,
host);
}
if (ctx->xdg_output_manager) {
host->expecting_logical_size = true;
host->zxdg_output = zxdg_output_manager_v1_get_xdg_output(
ctx->xdg_output_manager->internal, host->proxy);
zxdg_output_v1_add_listener(host->zxdg_output, &sl_xdg_output_listener,
host);
}
}
struct sl_global* sl_output_global_create(struct sl_output* output) {
return sl_global_create(output->ctx, &wl_output_interface, output->version,
output, sl_bind_host_output);
}

View File

@ -1,20 +1,20 @@
// Copyright 2019 The Chromium OS Authors. All rights reserved.
// Copyright 2019 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "sommelier.h"
#include "sommelier.h" // NOLINT(build/include_directory)
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
#include <wayland-client-protocol.h>
#include <wayland-client.h>
#include <wayland-client-protocol.h>
#include <wayland-server-core.h>
#include "pointer-constraints-unstable-v1-server-protocol.h"
#include "pointer-constraints-unstable-v1-client-protocol.h"
#include "pointer-constraints-unstable-v1-client-protocol.h" // NOLINT(build/include_directory)
#include "pointer-constraints-unstable-v1-server-protocol.h" // NOLINT(build/include_directory)
#define SL_HOST_OBJECT(NAME, INTERFACE) \
struct sl_host_##NAME { \
@ -22,11 +22,13 @@
struct wl_resource* resource; \
struct INTERFACE* proxy; \
}; \
MAP_STRUCTS(INTERFACE, sl_host_##NAME); \
static void sl_destroy_host_##NAME(struct wl_resource* resource) { \
struct sl_host_##NAME* host = wl_resource_get_user_data(resource); \
struct sl_host_##NAME* host = \
static_cast<sl_host_##NAME*>(wl_resource_get_user_data(resource)); \
INTERFACE##_destroy(host->proxy); \
wl_resource_set_user_data(resource, NULL); \
free(host); \
delete host; \
} \
static void sl_##NAME##_destroy(struct wl_client* client, \
struct wl_resource* resource) { \
@ -41,55 +43,38 @@ SL_HOST_OBJECT(confined_pointer, zwp_confined_pointer_v1);
static void sl_locked_pointer_locked(
void* data, struct zwp_locked_pointer_v1* locked_pointer) {
struct sl_host_locked_pointer* host =
zwp_locked_pointer_v1_get_user_data(locked_pointer);
struct sl_host_locked_pointer* host = static_cast<sl_host_locked_pointer*>(
zwp_locked_pointer_v1_get_user_data(locked_pointer));
zwp_locked_pointer_v1_send_locked(host->resource);
}
static void sl_locked_pointer_unlocked(
void* data, struct zwp_locked_pointer_v1* locked_pointer) {
struct sl_host_locked_pointer* host =
zwp_locked_pointer_v1_get_user_data(locked_pointer);
struct sl_host_locked_pointer* host = static_cast<sl_host_locked_pointer*>(
zwp_locked_pointer_v1_get_user_data(locked_pointer));
zwp_locked_pointer_v1_send_unlocked(host->resource);
}
static void sl_locked_pointer_set_cursor_position_hint(
struct wl_client* client,
struct wl_resource* resource,
wl_fixed_t surface_x,
wl_fixed_t surface_y) {
struct sl_host_locked_pointer* host = wl_resource_get_user_data(resource);
zwp_locked_pointer_v1_set_cursor_position_hint(host->proxy, surface_x,
surface_y);
}
static void sl_locked_pointer_set_region(struct wl_client* client,
struct wl_resource* resource,
struct wl_resource* region) {
struct sl_host_locked_pointer* host = wl_resource_get_user_data(resource);
struct sl_host_region* host_region =
region ? wl_resource_get_user_data(region) : NULL;
zwp_locked_pointer_v1_set_region(host->proxy,
host_region ? host_region->proxy : NULL);
}
static struct zwp_locked_pointer_v1_listener sl_locked_pointer_listener = {
sl_locked_pointer_locked,
sl_locked_pointer_unlocked,
};
static struct zwp_locked_pointer_v1_interface sl_locked_pointer_implementation =
{sl_locked_pointer_destroy, sl_locked_pointer_set_cursor_position_hint,
sl_locked_pointer_set_region};
{
sl_locked_pointer_destroy,
ForwardRequest<zwp_locked_pointer_v1_set_cursor_position_hint>,
ForwardRequest<zwp_locked_pointer_v1_set_region,
AllowNullResource::kYes>,
};
static void sl_confined_pointer_confined(
void* data, struct zwp_confined_pointer_v1* confined_pointer) {
struct sl_host_confined_pointer* host =
zwp_confined_pointer_v1_get_user_data(confined_pointer);
static_cast<sl_host_confined_pointer*>(
zwp_confined_pointer_v1_get_user_data(confined_pointer));
zwp_confined_pointer_v1_send_confined(host->resource);
}
@ -97,22 +82,12 @@ static void sl_confined_pointer_confined(
static void sl_confined_pointer_unconfined(
void* data, struct zwp_confined_pointer_v1* confined_pointer) {
struct sl_host_confined_pointer* host =
zwp_confined_pointer_v1_get_user_data(confined_pointer);
static_cast<sl_host_confined_pointer*>(
zwp_confined_pointer_v1_get_user_data(confined_pointer));
zwp_confined_pointer_v1_send_unconfined(host->resource);
}
static void sl_confined_pointer_set_region(struct wl_client* client,
struct wl_resource* resource,
struct wl_resource* region) {
struct sl_host_confined_pointer* host = wl_resource_get_user_data(resource);
struct sl_host_region* host_region =
region ? wl_resource_get_user_data(region) : NULL;
zwp_confined_pointer_v1_set_region(host->proxy,
host_region ? host_region->proxy : NULL);
}
static struct zwp_confined_pointer_v1_listener sl_confined_pointer_listener = {
sl_confined_pointer_confined,
sl_confined_pointer_unconfined,
@ -121,7 +96,8 @@ static struct zwp_confined_pointer_v1_listener sl_confined_pointer_listener = {
static struct zwp_confined_pointer_v1_interface
sl_confined_pointer_implementation = {
sl_confined_pointer_destroy,
sl_confined_pointer_set_region,
ForwardRequest<zwp_confined_pointer_v1_set_region,
AllowNullResource::kYes>,
};
static void sl_pointer_constraints_lock_pointer(struct wl_client* client,
@ -132,32 +108,33 @@ static void sl_pointer_constraints_lock_pointer(struct wl_client* client,
struct wl_resource* region,
uint32_t lifetime) {
struct sl_host_pointer_constraints* host =
wl_resource_get_user_data(resource);
static_cast<sl_host_pointer_constraints*>(
wl_resource_get_user_data(resource));
struct wl_resource* locked_pointer_resource =
wl_resource_create(client, &zwp_locked_pointer_v1_interface, 1, id);
struct sl_host_locked_pointer* locked_pointer_host;
struct sl_host_surface* host_surface = wl_resource_get_user_data(surface);
struct sl_host_pointer* host_pointer = wl_resource_get_user_data(pointer);
struct sl_host_surface* host_surface =
static_cast<sl_host_surface*>(wl_resource_get_user_data(surface));
struct sl_host_pointer* host_pointer =
static_cast<sl_host_pointer*>(wl_resource_get_user_data(pointer));
struct sl_host_region* host_region =
region ? wl_resource_get_user_data(region) : NULL;
region ? static_cast<sl_host_region*>(wl_resource_get_user_data(region))
: NULL;
locked_pointer_host = malloc(sizeof(struct sl_host_locked_pointer));
assert(locked_pointer_host);
struct sl_host_locked_pointer* locked_pointer_host =
new sl_host_locked_pointer();
locked_pointer_host->resource = locked_pointer_resource;
locked_pointer_host->ctx = host->ctx;
locked_pointer_host->proxy = zwp_pointer_constraints_v1_lock_pointer(
host->ctx->pointer_constraints->internal, host_surface->proxy,
host_pointer->proxy, host_region ? host_region->proxy : NULL, lifetime);
host->proxy, host_surface->proxy, host_pointer->proxy,
host_region ? host_region->proxy : NULL, lifetime);
wl_resource_set_implementation(
locked_pointer_resource, &sl_locked_pointer_implementation,
locked_pointer_host, sl_destroy_host_locked_pointer);
zwp_locked_pointer_v1_set_user_data(locked_pointer_host->proxy,
locked_pointer_host);
zwp_locked_pointer_v1_add_listener(locked_pointer_host->proxy,
&sl_locked_pointer_listener,
locked_pointer_host);
}
} // NOLINT(whitespace/indent)
static void sl_pointer_constraints_confine_pointer(struct wl_client* client,
struct wl_resource* resource,
@ -167,32 +144,33 @@ static void sl_pointer_constraints_confine_pointer(struct wl_client* client,
struct wl_resource* region,
uint32_t lifetime) {
struct sl_host_pointer_constraints* host =
wl_resource_get_user_data(resource);
static_cast<sl_host_pointer_constraints*>(
wl_resource_get_user_data(resource));
struct wl_resource* confined_pointer_resource =
wl_resource_create(client, &zwp_confined_pointer_v1_interface, 1, id);
struct sl_host_confined_pointer* confined_pointer_host;
struct sl_host_surface* host_surface = wl_resource_get_user_data(surface);
struct sl_host_pointer* host_pointer = wl_resource_get_user_data(pointer);
struct sl_host_surface* host_surface =
static_cast<sl_host_surface*>(wl_resource_get_user_data(surface));
struct sl_host_pointer* host_pointer =
static_cast<sl_host_pointer*>(wl_resource_get_user_data(pointer));
struct sl_host_region* host_region =
region ? wl_resource_get_user_data(region) : NULL;
region ? static_cast<sl_host_region*>(wl_resource_get_user_data(region))
: NULL;
confined_pointer_host = malloc(sizeof(struct sl_host_confined_pointer));
assert(confined_pointer_host);
struct sl_host_confined_pointer* confined_pointer_host =
new sl_host_confined_pointer();
confined_pointer_host->resource = confined_pointer_resource;
confined_pointer_host->ctx = host->ctx;
confined_pointer_host->proxy = zwp_pointer_constraints_v1_confine_pointer(
host->ctx->pointer_constraints->internal, host_surface->proxy,
host_pointer->proxy, host_region ? host_region->proxy : NULL, lifetime);
host->proxy, host_surface->proxy, host_pointer->proxy,
host_region ? host_region->proxy : NULL, lifetime);
wl_resource_set_implementation(
confined_pointer_resource, &sl_confined_pointer_implementation,
confined_pointer_host, sl_destroy_host_confined_pointer);
zwp_confined_pointer_v1_set_user_data(confined_pointer_host->proxy,
confined_pointer_host);
zwp_confined_pointer_v1_add_listener(confined_pointer_host->proxy,
&sl_confined_pointer_listener,
confined_pointer_host);
}
} // NOLINT(whitespace/indent)
static struct zwp_pointer_constraints_v1_interface
sl_pointer_constraints_implementation = {
@ -207,20 +185,18 @@ static void sl_bind_host_pointer_constraints(struct wl_client* client,
uint32_t id) {
struct sl_context* ctx = (struct sl_context*)data;
struct sl_pointer_constraints* pointer_constraints = ctx->pointer_constraints;
struct sl_host_pointer_constraints* host =
malloc(sizeof(struct sl_host_pointer_constraints));
struct sl_host_pointer_constraints* host = new sl_host_pointer_constraints();
assert(host);
host->ctx = ctx;
host->resource =
wl_resource_create(client, &zwp_pointer_constraints_v1_interface, 1, id);
wl_resource_set_implementation(host->resource,
&sl_pointer_constraints_implementation, host,
sl_destroy_host_pointer_constraints);
host->proxy = wl_registry_bind(wl_display_get_registry(ctx->display),
pointer_constraints->id,
host->proxy = static_cast<zwp_pointer_constraints_v1*>(wl_registry_bind(
wl_display_get_registry(ctx->display), pointer_constraints->id,
&zwp_pointer_constraints_v1_interface,
wl_resource_get_version(host->resource));
wl_resource_get_version(host->resource)));
zwp_pointer_constraints_v1_set_user_data(host->proxy, host);
}

View File

@ -1,19 +1,21 @@
// Copyright 2019 The Chromium OS Authors. All rights reserved.
// Copyright 2019 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "sommelier.h"
#include "sommelier.h" // NOLINT(build/include_directory)
#include <assert.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
#include <wayland-client.h>
#include <wayland-server-core.h>
#include <wayland-util.h>
#include "relative-pointer-unstable-v1-server-protocol.h"
#include "relative-pointer-unstable-v1-client-protocol.h"
#include "relative-pointer-unstable-v1-client-protocol.h" // NOLINT(build/include_directory)
#include "relative-pointer-unstable-v1-server-protocol.h" // NOLINT(build/include_directory)
struct sl_host_relative_pointer_manager {
struct sl_context* ctx;
@ -27,6 +29,14 @@ struct sl_host_relative_pointer {
struct zwp_relative_pointer_v1* proxy;
};
// Like ceil(), but strictly increases the magnitude of the input value (i.e.
// trunc() but for increasing the magnitude).
wl_fixed_t magnitude_ceil(wl_fixed_t val) {
double temp = wl_fixed_to_double(val);
temp = temp > 0 ? ceil(temp) : floor(temp);
return wl_fixed_from_double(temp);
}
static void sl_relative_pointer_relative_motion(
void* data,
struct zwp_relative_pointer_v1* relative_pointer,
@ -37,18 +47,29 @@ static void sl_relative_pointer_relative_motion(
wl_fixed_t dx_unaccel,
wl_fixed_t dy_unaccel) {
struct sl_host_relative_pointer* host =
zwp_relative_pointer_v1_get_user_data(relative_pointer);
static_cast<sl_host_relative_pointer*>(
zwp_relative_pointer_v1_get_user_data(relative_pointer));
// Unfortunately, many x11 toolkits truncate RawMotion events. We force them
// to interpret cursor movement by rounding to the next greater-magnitude
// value.
if (host->ctx->xwayland) {
dx_unaccel = magnitude_ceil(dx_unaccel);
dy_unaccel = magnitude_ceil(dy_unaccel);
}
zwp_relative_pointer_v1_send_relative_motion(
host->resource, utime_hi, utime_lo, dx, dy, dx_unaccel, dy_unaccel);
}
static void sl_destroy_host_relative_pointer(struct wl_resource* resource) {
struct sl_host_relative_pointer* host = wl_resource_get_user_data(resource);
struct sl_host_relative_pointer* host =
static_cast<sl_host_relative_pointer*>(
wl_resource_get_user_data(resource));
zwp_relative_pointer_v1_destroy(host->proxy);
wl_resource_set_user_data(resource, NULL);
free(host);
delete host;
}
static void sl_relative_pointer_destroy(struct wl_client* client,
@ -68,11 +89,12 @@ static struct zwp_relative_pointer_v1_interface
static void sl_destroy_host_relative_pointer_manager(
struct wl_resource* resource) {
struct sl_host_relative_pointer_manager* host =
wl_resource_get_user_data(resource);
static_cast<sl_host_relative_pointer_manager*>(
wl_resource_get_user_data(resource));
zwp_relative_pointer_manager_v1_destroy(host->proxy);
wl_resource_set_user_data(resource, NULL);
free(host);
delete host;
}
static void sl_relative_pointer_manager_destroy(struct wl_client* client,
@ -86,28 +108,26 @@ static void sl_relative_pointer_manager_get_relative_pointer(
uint32_t id,
struct wl_resource* pointer) {
struct sl_host_relative_pointer_manager* host =
wl_resource_get_user_data(resource);
struct sl_host_pointer* host_pointer = wl_resource_get_user_data(pointer);
static_cast<sl_host_relative_pointer_manager*>(
wl_resource_get_user_data(resource));
struct sl_host_pointer* host_pointer =
static_cast<sl_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);
struct sl_host_relative_pointer* relative_pointer_host =
new sl_host_relative_pointer();
relative_pointer_host->resource = relative_pointer_resource;
relative_pointer_host->ctx = host->ctx;
relative_pointer_host->proxy =
zwp_relative_pointer_manager_v1_get_relative_pointer(
host->ctx->relative_pointer_manager->internal, host_pointer->proxy);
zwp_relative_pointer_manager_v1_get_relative_pointer(host->proxy,
host_pointer->proxy);
wl_resource_set_implementation(
relative_pointer_resource, &sl_relative_pointer_implementation,
relative_pointer_host, sl_destroy_host_relative_pointer);
zwp_relative_pointer_v1_set_user_data(relative_pointer_host->proxy,
relative_pointer_host);
zwp_relative_pointer_v1_add_listener(relative_pointer_host->proxy,
&sl_relative_pointer_listener,
relative_pointer_host);
}
} // NOLINT(whitespace/indent)
static struct zwp_relative_pointer_manager_v1_interface
sl_relative_pointer_manager_implementation = {
@ -122,20 +142,18 @@ static void sl_bind_host_relative_pointer_manager(struct wl_client* client,
struct sl_context* ctx = (struct sl_context*)data;
struct sl_relative_pointer_manager* relative_pointer_manager =
ctx->relative_pointer_manager;
struct sl_host_relative_pointer_manager* host;
host = malloc(sizeof(*host));
assert(host);
struct sl_host_relative_pointer_manager* host =
new sl_host_relative_pointer_manager();
host->ctx = ctx;
host->resource = wl_resource_create(
client, &zwp_relative_pointer_manager_v1_interface, 1, id);
wl_resource_set_implementation(
host->resource, &sl_relative_pointer_manager_implementation, host,
sl_destroy_host_relative_pointer_manager);
host->proxy = wl_registry_bind(wl_display_get_registry(ctx->display),
relative_pointer_manager->id,
host->proxy = static_cast<zwp_relative_pointer_manager_v1*>(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));
wl_resource_get_version(host->resource)));
zwp_relative_pointer_manager_v1_set_user_data(host->proxy, host);
}

View File

@ -1,17 +1,20 @@
// Copyright 2018 The Chromium OS Authors. All rights reserved.
// Copyright 2018 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "sommelier.h"
#include "sommelier.h" // NOLINT(build/include_directory)
#include "sommelier-transform.h" // NOLINT(build/include_directory)
#include <assert.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
#include <wayland-client.h>
#include <wayland-util.h>
#include "keyboard-extension-unstable-v1-client-protocol.h"
#include "keyboard-extension-unstable-v1-client-protocol.h" // NOLINT(build/include_directory)
struct sl_host_keyboard {
struct sl_seat* seat;
@ -35,6 +38,7 @@ struct sl_host_touch {
struct wl_resource* resource;
struct wl_touch* proxy;
struct wl_resource* focus_resource;
struct sl_host_surface* focus_surface;
struct wl_listener focus_resource_listener;
};
@ -44,21 +48,26 @@ static void sl_host_pointer_set_cursor(struct wl_client* client,
struct wl_resource* surface_resource,
int32_t hotspot_x,
int32_t hotspot_y) {
struct sl_host_pointer* host = wl_resource_get_user_data(resource);
struct sl_host_pointer* host =
static_cast<sl_host_pointer*>(wl_resource_get_user_data(resource));
struct sl_host_surface* host_surface = NULL;
double scale = host->seat->ctx->scale;
int32_t hsx = hotspot_x;
int32_t hsy = hotspot_y;
if (surface_resource) {
host_surface = wl_resource_get_user_data(surface_resource);
host_surface = static_cast<sl_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);
}
sl_transform_guest_to_host(host->seat->ctx, nullptr, &hsx, &hsy);
wl_pointer_set_cursor(host->proxy, serial,
host_surface ? host_surface->proxy : NULL,
hotspot_x / scale, hotspot_y / scale);
}
host_surface ? host_surface->proxy : NULL, hsx, hsy);
} // NOLINT(whitespace/indent)
static void sl_host_pointer_release(struct wl_client* client,
struct wl_resource* resource) {
@ -70,8 +79,8 @@ static const struct wl_pointer_interface sl_pointer_implementation = {
static void sl_set_last_event_serial(struct wl_resource* surface_resource,
uint32_t serial) {
struct sl_host_surface* host_surface =
wl_resource_get_user_data(surface_resource);
struct sl_host_surface* host_surface = static_cast<sl_host_surface*>(
wl_resource_get_user_data(surface_resource));
host_surface->last_event_serial = serial;
}
@ -83,6 +92,8 @@ static void sl_pointer_set_focus(struct sl_host_pointer* host,
wl_fixed_t y) {
struct wl_resource* surface_resource =
host_surface ? host_surface->resource : NULL;
wl_fixed_t ix = x;
wl_fixed_t iy = y;
if (surface_resource == host->focus_resource)
return;
@ -94,10 +105,9 @@ static void sl_pointer_set_focus(struct sl_host_pointer* host,
wl_list_init(&host->focus_resource_listener.link);
host->focus_resource = surface_resource;
host->focus_serial = serial;
host->focus_surface = host_surface;
if (surface_resource) {
double scale = host->seat->ctx->scale;
if (host->seat->ctx->xwayland) {
// Make sure focus surface is on top before sending enter event.
sl_restack_windows(host->seat->ctx, wl_resource_get_id(surface_resource));
@ -107,8 +117,8 @@ static void sl_pointer_set_focus(struct sl_host_pointer* host,
wl_resource_add_destroy_listener(surface_resource,
&host->focus_resource_listener);
wl_pointer_send_enter(host->resource, serial, surface_resource, x * scale,
y * scale);
sl_transform_host_to_guest_fixed(host->seat->ctx, host_surface, &ix, &iy);
wl_pointer_send_enter(host->resource, serial, surface_resource, ix, iy);
}
}
@ -118,9 +128,11 @@ static void sl_pointer_enter(void* data,
struct wl_surface* surface,
wl_fixed_t x,
wl_fixed_t y) {
struct sl_host_pointer* host = wl_pointer_get_user_data(pointer);
struct sl_host_pointer* host =
static_cast<sl_host_pointer*>(wl_pointer_get_user_data(pointer));
struct sl_host_surface* host_surface =
surface ? wl_surface_get_user_data(surface) : NULL;
surface ? static_cast<sl_host_surface*>(wl_surface_get_user_data(surface))
: NULL;
if (!host_surface)
return;
@ -130,13 +142,14 @@ static void sl_pointer_enter(void* data,
if (host->focus_resource)
sl_set_last_event_serial(host->focus_resource, serial);
host->seat->last_serial = serial;
}
} // NOLINT(whitespace/indent)
static void sl_pointer_leave(void* data,
struct wl_pointer* pointer,
uint32_t serial,
struct wl_surface* surface) {
struct sl_host_pointer* host = wl_pointer_get_user_data(pointer);
struct sl_host_pointer* host =
static_cast<sl_host_pointer*>(wl_pointer_get_user_data(pointer));
sl_pointer_set_focus(host, serial, NULL, 0, 0);
}
@ -146,10 +159,15 @@ static void sl_pointer_motion(void* data,
uint32_t time,
wl_fixed_t x,
wl_fixed_t y) {
struct sl_host_pointer* host = wl_pointer_get_user_data(pointer);
double scale = host->seat->ctx->scale;
struct sl_host_pointer* host =
static_cast<sl_host_pointer*>(wl_pointer_get_user_data(pointer));
wl_pointer_send_motion(host->resource, time, x * scale, y * scale);
wl_fixed_t mx = x;
wl_fixed_t my = y;
sl_transform_host_to_guest_fixed(host->seat->ctx, host->focus_surface, &mx,
&my);
wl_pointer_send_motion(host->resource, time, mx, my);
}
static void sl_pointer_button(void* data,
@ -158,7 +176,8 @@ static void sl_pointer_button(void* data,
uint32_t time,
uint32_t button,
uint32_t state) {
struct sl_host_pointer* host = wl_pointer_get_user_data(pointer);
struct sl_host_pointer* host =
static_cast<sl_host_pointer*>(wl_pointer_get_user_data(pointer));
wl_pointer_send_button(host->resource, serial, time, button, state);
@ -172,14 +191,51 @@ static void sl_pointer_axis(void* data,
uint32_t time,
uint32_t axis,
wl_fixed_t value) {
struct sl_host_pointer* host = wl_pointer_get_user_data(pointer);
double scale = host->seat->ctx->scale;
struct sl_host_pointer* host =
static_cast<sl_host_pointer*>(wl_pointer_get_user_data(pointer));
wl_fixed_t svalue = value;
wl_pointer_send_axis(host->resource, time, axis, value * scale);
sl_transform_host_to_guest_fixed(host->seat->ctx, nullptr, &svalue, axis);
host->time = time;
host->axis_delta[axis] += svalue;
}
static void sl_pointer_frame(void* data, struct wl_pointer* pointer) {
struct sl_host_pointer* host = wl_pointer_get_user_data(pointer);
struct sl_host_pointer* host =
static_cast<sl_host_pointer*>(wl_pointer_get_user_data(pointer));
// Many X apps (e.g. VS Code, Firefox, Chromium) only allow scrolls to happen
// in multiples of 5 units. This value comes from the smooth scrolling
// extension of X, which says that 5 smooth scroll units is equal to 1 tick of
// discrete scrolling.
//
// To avoid the experience of scrolling and seeing nothing happen, we replace
// scroll amounts < 5 with 5 units, for discrete scrolls for X apps
// only. Other clients should continue to see smaller scrolls as they can
// handle them correctly, and for non-discrete scrolling (such as touch-pads)
// we don't want to do this because it would lead to erratic jumps.
const int kDiscreteScrollUnit = 5;
for (int axis = 0; axis < 2; axis++) {
if (host->axis_discrete[axis] != 0) {
wl_pointer_send_axis_discrete(host->resource, axis,
host->axis_discrete[axis]);
double axis_delta = wl_fixed_to_double(host->axis_delta[axis]);
if (fabs(axis_delta) < kDiscreteScrollUnit && host->seat->ctx->xwayland) {
axis_delta = copysign(kDiscreteScrollUnit, axis_delta);
host->axis_delta[axis] = wl_fixed_from_double(axis_delta);
}
}
if (host->axis_delta[axis] != 0) {
wl_pointer_send_axis(host->resource, host->time, axis,
host->axis_delta[axis]);
}
host->axis_delta[axis] = wl_fixed_from_int(0);
host->axis_discrete[axis] = 0;
}
wl_pointer_send_frame(host->resource);
}
@ -187,7 +243,8 @@ static void sl_pointer_frame(void* data, struct wl_pointer* pointer) {
void sl_pointer_axis_source(void* data,
struct wl_pointer* pointer,
uint32_t axis_source) {
struct sl_host_pointer* host = wl_pointer_get_user_data(pointer);
struct sl_host_pointer* host =
static_cast<sl_host_pointer*>(wl_pointer_get_user_data(pointer));
wl_pointer_send_axis_source(host->resource, axis_source);
}
@ -196,7 +253,8 @@ static void sl_pointer_axis_stop(void* data,
struct wl_pointer* pointer,
uint32_t time,
uint32_t axis) {
struct sl_host_pointer* host = wl_pointer_get_user_data(pointer);
struct sl_host_pointer* host =
static_cast<sl_host_pointer*>(wl_pointer_get_user_data(pointer));
wl_pointer_send_axis_stop(host->resource, time, axis);
}
@ -205,9 +263,10 @@ static void sl_pointer_axis_discrete(void* data,
struct wl_pointer* pointer,
uint32_t axis,
int32_t discrete) {
struct sl_host_pointer* host = wl_pointer_get_user_data(pointer);
struct sl_host_pointer* host =
static_cast<sl_host_pointer*>(wl_pointer_get_user_data(pointer));
wl_pointer_send_axis_discrete(host->resource, axis, discrete);
host->axis_discrete[axis] += discrete;
}
static const struct wl_pointer_listener sl_pointer_listener = {
@ -228,7 +287,8 @@ static void sl_keyboard_keymap(void* data,
uint32_t format,
int32_t fd,
uint32_t size) {
struct sl_host_keyboard* host = wl_keyboard_get_user_data(keyboard);
struct sl_host_keyboard* host =
static_cast<sl_host_keyboard*>(wl_keyboard_get_user_data(keyboard));
wl_keyboard_send_keymap(host->resource, format, fd, size);
@ -241,7 +301,9 @@ static void sl_keyboard_keymap(void* data,
xkb_keymap_unref(host->keymap);
host->keymap = xkb_keymap_new_from_string(
host->seat->ctx->xkb_context, data, XKB_KEYMAP_FORMAT_TEXT_V1, 0);
host->seat->ctx->xkb_context, static_cast<char*>(data),
XKB_KEYMAP_FORMAT_TEXT_V1,
static_cast<xkb_keymap_compile_flags>(XKB_KEYMAP_COMPILE_NO_FLAGS));
assert(host->keymap);
munmap(data, size);
@ -291,9 +353,11 @@ static void sl_keyboard_enter(void* data,
uint32_t serial,
struct wl_surface* surface,
struct wl_array* keys) {
struct sl_host_keyboard* host = wl_keyboard_get_user_data(keyboard);
struct sl_host_keyboard* host =
static_cast<sl_host_keyboard*>(wl_keyboard_get_user_data(keyboard));
struct sl_host_surface* host_surface =
surface ? wl_surface_get_user_data(surface) : NULL;
surface ? static_cast<sl_host_surface*>(wl_surface_get_user_data(surface))
: NULL;
if (!host_surface)
return;
@ -302,13 +366,14 @@ static void sl_keyboard_enter(void* data,
sl_keyboard_set_focus(host, serial, host_surface, keys);
host->seat->last_serial = serial;
}
} // NOLINT(whitespace/indent)
static void sl_keyboard_leave(void* data,
struct wl_keyboard* keyboard,
uint32_t serial,
struct wl_surface* surface) {
struct sl_host_keyboard* host = wl_keyboard_get_user_data(keyboard);
struct sl_host_keyboard* host =
static_cast<sl_host_keyboard*>(wl_keyboard_get_user_data(keyboard));
struct wl_array array;
wl_array_init(&array);
@ -318,11 +383,11 @@ static void sl_keyboard_leave(void* data,
static int sl_array_set_add(struct wl_array* array, uint32_t key) {
uint32_t* k;
wl_array_for_each(k, array) {
sl_array_for_each(k, array) {
if (*k == key)
return 0;
}
k = wl_array_add(array, sizeof(key));
k = static_cast<uint32_t*>(wl_array_add(array, sizeof(key)));
assert(k);
*k = key;
return 1;
@ -331,9 +396,10 @@ static int sl_array_set_add(struct wl_array* array, uint32_t key) {
static int sl_array_set_remove(struct wl_array* array, uint32_t key) {
uint32_t* k;
wl_array_for_each(k, array) {
sl_array_for_each(k, array) {
if (*k == key) {
uint32_t* end = (uint32_t*)((char*)array->data + array->size);
uint32_t* end = reinterpret_cast<uint32_t*>(
reinterpret_cast<char*>(array->data) + array->size);
*k = *(end - 1);
array->size -= sizeof(*k);
@ -349,8 +415,9 @@ static void sl_keyboard_key(void* data,
uint32_t time,
uint32_t key,
uint32_t state) {
struct sl_host_keyboard* host = wl_keyboard_get_user_data(keyboard);
int handled = 1;
struct sl_host_keyboard* host =
static_cast<sl_host_keyboard*>(wl_keyboard_get_user_data(keyboard));
bool handled = true;
if (state == WL_KEYBOARD_KEY_STATE_PRESSED) {
if (host->state) {
@ -365,16 +432,25 @@ static void sl_keyboard_key(void* data,
symbol = symbols[0];
wl_list_for_each(accelerator, &host->seat->ctx->accelerators, link) {
if (host->modifiers != accelerator->modifiers)
continue;
if (symbol != accelerator->symbol)
continue;
handled = 0;
if (host->modifiers == accelerator->modifiers &&
xkb_keysym_to_lower(symbol) == accelerator->symbol) {
handled = false;
break;
}
}
if (host->seat->ctx->host_focus_window &&
!(host->seat->ctx->host_focus_window->fullscreen ||
host->seat->ctx->host_focus_window->compositor_fullscreen)) {
wl_list_for_each(accelerator, &host->seat->ctx->windowed_accelerators,
link) {
if (host->modifiers == accelerator->modifiers &&
xkb_keysym_to_lower(symbol) == accelerator->symbol) {
handled = false;
break;
}
}
}
}
// Forward key pressed event if it should be handled and not
// already pressed.
if (handled) {
@ -407,7 +483,8 @@ static void sl_keyboard_modifiers(void* data,
uint32_t mods_latched,
uint32_t mods_locked,
uint32_t group) {
struct sl_host_keyboard* host = wl_keyboard_get_user_data(keyboard);
struct sl_host_keyboard* host =
static_cast<sl_host_keyboard*>(wl_keyboard_get_user_data(keyboard));
xkb_mod_mask_t mask;
wl_keyboard_send_modifiers(host->resource, serial, mods_depressed,
@ -423,7 +500,8 @@ static void sl_keyboard_modifiers(void* data,
xkb_state_update_mask(host->state, mods_depressed, mods_latched, mods_locked,
0, 0, group);
mask = xkb_state_serialize_mods(
host->state, XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_LATCHED);
host->state, static_cast<xkb_state_component>(XKB_STATE_MODS_DEPRESSED |
XKB_STATE_MODS_LATCHED));
host->modifiers = 0;
if (mask & host->control_mask)
host->modifiers |= CONTROL_MASK;
@ -437,7 +515,8 @@ static void sl_keyboard_repeat_info(void* data,
struct wl_keyboard* keyboard,
int32_t rate,
int32_t delay) {
struct sl_host_keyboard* host = wl_keyboard_get_user_data(keyboard);
struct sl_host_keyboard* host =
static_cast<sl_host_keyboard*>(wl_keyboard_get_user_data(keyboard));
wl_keyboard_send_repeat_info(host->resource, rate, delay);
}
@ -462,10 +541,14 @@ static void sl_host_touch_down(void* data,
int32_t id,
wl_fixed_t x,
wl_fixed_t y) {
struct sl_host_touch* host = wl_touch_get_user_data(touch);
struct sl_host_touch* host =
static_cast<sl_host_touch*>(wl_touch_get_user_data(touch));
struct sl_host_surface* host_surface =
surface ? wl_surface_get_user_data(surface) : NULL;
double scale = host->seat->ctx->scale;
surface ? static_cast<sl_host_surface*>(wl_surface_get_user_data(surface))
: NULL;
wl_fixed_t ix = x;
wl_fixed_t iy = y;
if (!host_surface)
return;
@ -474,6 +557,7 @@ static void sl_host_touch_down(void* data,
wl_list_remove(&host->focus_resource_listener.link);
wl_list_init(&host->focus_resource_listener.link);
host->focus_resource = host_surface->resource;
host->focus_surface = host_surface;
wl_resource_add_destroy_listener(host_surface->resource,
&host->focus_resource_listener);
}
@ -485,24 +569,27 @@ static void sl_host_touch_down(void* data,
sl_roundtrip(host->seat->ctx);
}
sl_transform_host_to_guest_fixed(host->seat->ctx, host_surface, &ix, &iy);
wl_touch_send_down(host->resource, serial, time, host_surface->resource, id,
x * scale, y * scale);
ix, iy);
if (host->focus_resource)
sl_set_last_event_serial(host->focus_resource, serial);
host->seat->last_serial = serial;
}
} // NOLINT(whitespace/indent)
static void sl_host_touch_up(void* data,
struct wl_touch* touch,
uint32_t serial,
uint32_t time,
int32_t id) {
struct sl_host_touch* host = wl_touch_get_user_data(touch);
struct sl_host_touch* host =
static_cast<sl_host_touch*>(wl_touch_get_user_data(touch));
wl_list_remove(&host->focus_resource_listener.link);
wl_list_init(&host->focus_resource_listener.link);
host->focus_resource = NULL;
host->focus_surface = NULL;
wl_touch_send_up(host->resource, serial, time, id);
@ -517,20 +604,26 @@ static void sl_host_touch_motion(void* data,
int32_t id,
wl_fixed_t x,
wl_fixed_t y) {
struct sl_host_touch* host = wl_touch_get_user_data(touch);
double scale = host->seat->ctx->scale;
struct sl_host_touch* host =
static_cast<sl_host_touch*>(wl_touch_get_user_data(touch));
wl_fixed_t ix = x;
wl_fixed_t iy = y;
wl_touch_send_motion(host->resource, time, id, x * scale, y * scale);
sl_transform_host_to_guest_fixed(host->seat->ctx, host->focus_surface, &ix,
&iy);
wl_touch_send_motion(host->resource, time, id, ix, iy);
}
static void sl_host_touch_frame(void* data, struct wl_touch* touch) {
struct sl_host_touch* host = wl_touch_get_user_data(touch);
struct sl_host_touch* host =
static_cast<sl_host_touch*>(wl_touch_get_user_data(touch));
wl_touch_send_frame(host->resource);
}
static void sl_host_touch_cancel(void* data, struct wl_touch* touch) {
struct sl_host_touch* host = wl_touch_get_user_data(touch);
struct sl_host_touch* host =
static_cast<sl_host_touch*>(wl_touch_get_user_data(touch));
wl_touch_send_cancel(host->resource);
}
@ -540,7 +633,8 @@ static const struct wl_touch_listener sl_touch_listener = {
sl_host_touch_frame, sl_host_touch_cancel};
static void sl_destroy_host_pointer(struct wl_resource* resource) {
struct sl_host_pointer* host = wl_resource_get_user_data(resource);
struct sl_host_pointer* host =
static_cast<sl_host_pointer*>(wl_resource_get_user_data(resource));
if (wl_pointer_get_version(host->proxy) >= WL_POINTER_RELEASE_SINCE_VERSION) {
wl_pointer_release(host->proxy);
@ -549,7 +643,7 @@ static void sl_destroy_host_pointer(struct wl_resource* resource) {
}
wl_list_remove(&host->focus_resource_listener.link);
wl_resource_set_user_data(resource, NULL);
free(host);
delete host;
}
static void sl_pointer_focus_resource_destroyed(struct wl_listener* listener,
@ -563,11 +657,9 @@ static void sl_pointer_focus_resource_destroyed(struct wl_listener* listener,
static void sl_host_seat_get_host_pointer(struct wl_client* client,
struct wl_resource* resource,
uint32_t id) {
struct sl_host_seat* host = wl_resource_get_user_data(resource);
struct sl_host_pointer* host_pointer;
host_pointer = malloc(sizeof(*host_pointer));
assert(host_pointer);
struct sl_host_seat* host =
static_cast<sl_host_seat*>(wl_resource_get_user_data(resource));
struct sl_host_pointer* host_pointer = new sl_host_pointer();
host_pointer->seat = host->seat;
host_pointer->resource = wl_resource_create(
@ -576,18 +668,24 @@ static void sl_host_seat_get_host_pointer(struct wl_client* client,
&sl_pointer_implementation, host_pointer,
sl_destroy_host_pointer);
host_pointer->proxy = wl_seat_get_pointer(host->proxy);
wl_pointer_set_user_data(host_pointer->proxy, host_pointer);
wl_pointer_add_listener(host_pointer->proxy, &sl_pointer_listener,
host_pointer);
wl_list_init(&host_pointer->focus_resource_listener.link);
host_pointer->focus_resource_listener.notify =
sl_pointer_focus_resource_destroyed;
host_pointer->focus_resource = NULL;
host_pointer->focus_surface = NULL;
host_pointer->focus_serial = 0;
host_pointer->time = 0;
host_pointer->axis_delta[0] = wl_fixed_from_int(0);
host_pointer->axis_delta[1] = wl_fixed_from_int(0);
host_pointer->axis_discrete[0] = 0;
host_pointer->axis_discrete[1] = 0;
}
static void sl_destroy_host_keyboard(struct wl_resource* resource) {
struct sl_host_keyboard* host = wl_resource_get_user_data(resource);
struct sl_host_keyboard* host =
static_cast<sl_host_keyboard*>(wl_resource_get_user_data(resource));
if (host->extended_keyboard_proxy)
zcr_extended_keyboard_v1_destroy(host->extended_keyboard_proxy);
@ -607,7 +705,7 @@ static void sl_destroy_host_keyboard(struct wl_resource* resource) {
wl_list_remove(&host->focus_resource_listener.link);
wl_resource_set_user_data(resource, NULL);
free(host);
delete host;
}
static void sl_keyboard_focus_resource_destroyed(struct wl_listener* listener,
@ -623,11 +721,9 @@ static void sl_keyboard_focus_resource_destroyed(struct wl_listener* listener,
static void sl_host_seat_get_host_keyboard(struct wl_client* client,
struct wl_resource* resource,
uint32_t id) {
struct sl_host_seat* host = wl_resource_get_user_data(resource);
struct sl_host_keyboard* host_keyboard;
host_keyboard = malloc(sizeof(*host_keyboard));
assert(host_keyboard);
struct sl_host_seat* host =
static_cast<sl_host_seat*>(wl_resource_get_user_data(resource));
struct sl_host_keyboard* host_keyboard = new sl_host_keyboard();
host_keyboard->seat = host->seat;
host_keyboard->resource = wl_resource_create(
@ -636,7 +732,6 @@ static void sl_host_seat_get_host_keyboard(struct wl_client* client,
&sl_keyboard_implementation, host_keyboard,
sl_destroy_host_keyboard);
host_keyboard->proxy = wl_seat_get_keyboard(host->proxy);
wl_keyboard_set_user_data(host_keyboard->proxy, host_keyboard);
wl_keyboard_add_listener(host_keyboard->proxy, &sl_keyboard_listener,
host_keyboard);
wl_list_init(&host_keyboard->focus_resource_listener.link);
@ -663,7 +758,8 @@ static void sl_host_seat_get_host_keyboard(struct wl_client* client,
}
static void sl_destroy_host_touch(struct wl_resource* resource) {
struct sl_host_touch* host = wl_resource_get_user_data(resource);
struct sl_host_touch* host =
static_cast<sl_host_touch*>(wl_resource_get_user_data(resource));
if (wl_touch_get_version(host->proxy) >= WL_TOUCH_RELEASE_SINCE_VERSION) {
wl_touch_release(host->proxy);
@ -671,7 +767,7 @@ static void sl_destroy_host_touch(struct wl_resource* resource) {
wl_touch_destroy(host->proxy);
}
wl_resource_set_user_data(resource, NULL);
free(host);
delete host;
}
static void sl_touch_focus_resource_destroyed(struct wl_listener* listener,
@ -682,16 +778,15 @@ static void sl_touch_focus_resource_destroyed(struct wl_listener* listener,
wl_list_remove(&host->focus_resource_listener.link);
wl_list_init(&host->focus_resource_listener.link);
host->focus_resource = NULL;
host->focus_surface = NULL;
}
static void sl_host_seat_get_host_touch(struct wl_client* client,
struct wl_resource* resource,
uint32_t id) {
struct sl_host_seat* host = wl_resource_get_user_data(resource);
struct sl_host_touch* host_touch;
host_touch = malloc(sizeof(*host_touch));
assert(host_touch);
struct sl_host_seat* host =
static_cast<sl_host_seat*>(wl_resource_get_user_data(resource));
struct sl_host_touch* host_touch = new sl_host_touch();
host_touch->seat = host->seat;
host_touch->resource = wl_resource_create(
@ -699,35 +794,30 @@ static void sl_host_seat_get_host_touch(struct wl_client* client,
wl_resource_set_implementation(host_touch->resource, &sl_touch_implementation,
host_touch, sl_destroy_host_touch);
host_touch->proxy = wl_seat_get_touch(host->proxy);
wl_touch_set_user_data(host_touch->proxy, host_touch);
wl_touch_add_listener(host_touch->proxy, &sl_touch_listener, host_touch);
wl_list_init(&host_touch->focus_resource_listener.link);
host_touch->focus_resource_listener.notify =
sl_touch_focus_resource_destroyed;
host_touch->focus_resource = NULL;
}
static void sl_host_seat_release(struct wl_client* client,
struct wl_resource* resource) {
struct sl_host_seat* host = wl_resource_get_user_data(resource);
wl_seat_release(host->proxy);
host_touch->focus_surface = NULL;
}
static const struct wl_seat_interface sl_seat_implementation = {
sl_host_seat_get_host_pointer, sl_host_seat_get_host_keyboard,
sl_host_seat_get_host_touch, sl_host_seat_release};
sl_host_seat_get_host_touch, ForwardRequest<wl_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);
struct sl_host_seat* host =
static_cast<sl_host_seat*>(wl_seat_get_user_data(seat));
wl_seat_send_capabilities(host->resource, capabilities);
}
static void sl_seat_name(void* data, struct wl_seat* seat, const char* name) {
struct sl_host_seat* host = wl_seat_get_user_data(seat);
struct sl_host_seat* host =
static_cast<sl_host_seat*>(wl_seat_get_user_data(seat));
if (wl_resource_get_version(host->resource) >= WL_SEAT_NAME_SINCE_VERSION)
wl_seat_send_name(host->resource, name);
@ -737,7 +827,8 @@ static const struct wl_seat_listener sl_seat_listener = {sl_seat_capabilities,
sl_seat_name};
static void sl_destroy_host_seat(struct wl_resource* resource) {
struct sl_host_seat* host = wl_resource_get_user_data(resource);
struct sl_host_seat* host =
static_cast<sl_host_seat*>(wl_resource_get_user_data(resource));
sl_host_seat_removed(host);
@ -746,7 +837,7 @@ static void sl_destroy_host_seat(struct wl_resource* resource) {
else
wl_seat_destroy(host->proxy);
wl_resource_set_user_data(resource, NULL);
free(host);
delete host;
}
static void sl_bind_host_seat(struct wl_client* client,
@ -754,19 +845,15 @@ static void sl_bind_host_seat(struct wl_client* client,
uint32_t version,
uint32_t id) {
struct sl_seat* seat = (struct sl_seat*)data;
struct sl_host_seat* host;
host = malloc(sizeof(*host));
assert(host);
struct sl_host_seat* host = new sl_host_seat();
host->seat = seat;
host->resource = wl_resource_create(client, &wl_seat_interface,
MIN(version, seat->version), id);
wl_resource_set_implementation(host->resource, &sl_seat_implementation, host,
sl_destroy_host_seat);
host->proxy = wl_registry_bind(wl_display_get_registry(seat->ctx->display),
seat->id, &wl_seat_interface,
wl_resource_get_version(host->resource));
wl_seat_set_user_data(host->proxy, host);
host->proxy = static_cast<wl_seat*>(wl_registry_bind(
wl_display_get_registry(seat->ctx->display), seat->id, &wl_seat_interface,
wl_resource_get_version(host->resource)));
wl_seat_add_listener(host->proxy, &sl_seat_listener, host);
sl_host_seat_added(host);

View File

@ -1,234 +0,0 @@
// Copyright 2018 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "sommelier.h"
#include <assert.h>
#include <stdlib.h>
#include <wayland-client.h>
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);
}

View File

@ -0,0 +1,135 @@
// Copyright 2018 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "sommelier.h" // NOLINT(build/include_directory)
#include "sommelier-tracing.h" // NOLINT(build/include_directory)
#include <assert.h>
#include <stdlib.h>
#include <wayland-client.h>
struct sl_host_shell_surface {
struct wl_resource* resource;
struct wl_shell_surface* proxy;
};
MAP_STRUCTS(wl_shell_surface, sl_host_shell_surface);
struct sl_host_shell {
struct sl_shell* shell;
struct wl_resource* resource;
struct wl_shell* proxy;
};
static const struct wl_shell_surface_interface sl_shell_surface_implementation =
{
ForwardRequest<wl_shell_surface_pong>,
ForwardRequest<wl_shell_surface_move>,
ForwardRequest<wl_shell_surface_resize>,
ForwardRequest<wl_shell_surface_set_toplevel>,
ForwardRequest<wl_shell_surface_set_transient>,
ForwardRequest<wl_shell_surface_set_fullscreen,
AllowNullResource::kYes>,
ForwardRequest<wl_shell_surface_set_popup>,
ForwardRequest<wl_shell_surface_set_maximized, AllowNullResource::kYes>,
ForwardRequest<wl_shell_surface_set_title>,
ForwardRequest<wl_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 = static_cast<sl_host_shell_surface*>(
wl_shell_surface_get_user_data(shell_surface));
wl_shell_surface_send_ping(host->resource, serial);
}
static void sl_shell_surface_configure(void* data,
struct wl_shell_surface* shell_surface,
uint32_t edges,
int32_t width,
int32_t height) {
TRACE_EVENT("shell", "sl_shell_surface_configure");
struct sl_host_shell_surface* host = static_cast<sl_host_shell_surface*>(
wl_shell_surface_get_user_data(shell_surface));
wl_shell_surface_send_configure(host->resource, edges, width, height);
}
static void sl_shell_surface_popup_done(
void* data, struct wl_shell_surface* shell_surface) {
struct sl_host_shell_surface* host = static_cast<sl_host_shell_surface*>(
wl_shell_surface_get_user_data(shell_surface));
wl_shell_surface_send_popup_done(host->resource);
}
static const struct wl_shell_surface_listener sl_shell_surface_listener = {
sl_shell_surface_ping, sl_shell_surface_configure,
sl_shell_surface_popup_done};
static void sl_destroy_host_shell_surface(struct wl_resource* resource) {
struct sl_host_shell_surface* host =
static_cast<sl_host_shell_surface*>(wl_resource_get_user_data(resource));
wl_shell_surface_destroy(host->proxy);
wl_resource_set_user_data(resource, NULL);
delete host;
}
static void sl_host_shell_get_shell_surface(
struct wl_client* client,
struct wl_resource* resource,
uint32_t id,
struct wl_resource* surface_resource) {
struct sl_host_shell* host =
static_cast<sl_host_shell*>(wl_resource_get_user_data(resource));
struct sl_host_surface* host_surface = static_cast<sl_host_surface*>(
wl_resource_get_user_data(surface_resource));
struct sl_host_shell_surface* host_shell_surface =
new sl_host_shell_surface();
host_shell_surface->resource =
wl_resource_create(client, &wl_shell_surface_interface, 1, id);
wl_resource_set_implementation(
host_shell_surface->resource, &sl_shell_surface_implementation,
host_shell_surface, sl_destroy_host_shell_surface);
host_shell_surface->proxy =
wl_shell_get_shell_surface(host->proxy, host_surface->proxy);
wl_shell_surface_add_listener(host_shell_surface->proxy,
&sl_shell_surface_listener, host_shell_surface);
host_surface->has_role = 1;
} // NOLINT(whitespace/indent)
static const struct wl_shell_interface sl_shell_implementation = {
sl_host_shell_get_shell_surface};
static void sl_destroy_host_shell(struct wl_resource* resource) {
struct sl_host_shell* host =
static_cast<sl_host_shell*>(wl_resource_get_user_data(resource));
wl_shell_destroy(host->proxy);
wl_resource_set_user_data(resource, NULL);
delete host;
}
static void sl_bind_host_shell(struct wl_client* client,
void* data,
uint32_t version,
uint32_t id) {
struct sl_context* ctx = (struct sl_context*)data;
struct sl_host_shell* host = new sl_host_shell();
assert(host);
host->shell = ctx->shell;
host->resource = wl_resource_create(client, &wl_shell_interface, 1, id);
wl_resource_set_implementation(host->resource, &sl_shell_implementation, host,
sl_destroy_host_shell);
host->proxy = static_cast<wl_shell*>(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);
}

View File

@ -1,8 +1,9 @@
// Copyright 2018 The Chromium OS Authors. All rights reserved.
// Copyright 2018 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "sommelier.h"
#include "sommelier.h" // NOLINT(build/include_directory)
#include "sommelier-transform.h" // NOLINT(build/include_directory)
#include <assert.h>
#include <stdlib.h>
@ -19,6 +20,7 @@ struct sl_host_subsurface {
struct wl_resource* resource;
struct wl_subsurface* proxy;
};
MAP_STRUCTS(wl_subsurface, sl_host_subsurface);
static void sl_subsurface_destroy(struct wl_client* client,
struct wl_resource* resource) {
@ -29,57 +31,31 @@ static void sl_subsurface_set_position(struct wl_client* client,
struct wl_resource* resource,
int32_t x,
int32_t y) {
struct sl_host_subsurface* host = wl_resource_get_user_data(resource);
double scale = host->ctx->scale;
struct sl_host_subsurface* host =
static_cast<sl_host_subsurface*>(wl_resource_get_user_data(resource));
int32_t ix = x;
int32_t iy = y;
wl_subsurface_set_position(host->proxy, x / scale, y / scale);
}
static void sl_subsurface_place_above(struct wl_client* client,
struct wl_resource* resource,
struct wl_resource* sibling_resource) {
struct sl_host_subsurface* host = wl_resource_get_user_data(resource);
struct sl_host_surface* host_sibling =
wl_resource_get_user_data(sibling_resource);
wl_subsurface_place_above(host->proxy, host_sibling->proxy);
}
static void sl_subsurface_place_below(struct wl_client* client,
struct wl_resource* resource,
struct wl_resource* sibling_resource) {
struct sl_host_subsurface* host = wl_resource_get_user_data(resource);
struct sl_host_surface* host_sibling =
wl_resource_get_user_data(sibling_resource);
wl_subsurface_place_below(host->proxy, host_sibling->proxy);
}
static void sl_subsurface_set_sync(struct wl_client* client,
struct wl_resource* resource) {
struct sl_host_subsurface* host = wl_resource_get_user_data(resource);
wl_subsurface_set_sync(host->proxy);
}
static void sl_subsurface_set_desync(struct wl_client* client,
struct wl_resource* resource) {
struct sl_host_subsurface* host = wl_resource_get_user_data(resource);
wl_subsurface_set_desync(host->proxy);
sl_transform_guest_to_host(host->ctx, nullptr, &ix, &iy);
wl_subsurface_set_position(host->proxy, ix, iy);
}
static const struct wl_subsurface_interface sl_subsurface_implementation = {
sl_subsurface_destroy, sl_subsurface_set_position,
sl_subsurface_place_above, sl_subsurface_place_below,
sl_subsurface_set_sync, sl_subsurface_set_desync};
sl_subsurface_destroy,
sl_subsurface_set_position,
ForwardRequest<wl_subsurface_place_above>,
ForwardRequest<wl_subsurface_place_below>,
ForwardRequest<wl_subsurface_set_sync>,
ForwardRequest<wl_subsurface_set_desync>,
};
static void sl_destroy_host_subsurface(struct wl_resource* resource) {
struct sl_host_subsurface* host = wl_resource_get_user_data(resource);
struct sl_host_subsurface* host =
static_cast<sl_host_subsurface*>(wl_resource_get_user_data(resource));
wl_subsurface_destroy(host->proxy);
wl_resource_set_user_data(resource, NULL);
free(host);
delete host;
}
static void sl_subcompositor_destroy(struct wl_client* client,
@ -93,15 +69,13 @@ static void sl_subcompositor_get_subsurface(
uint32_t id,
struct wl_resource* surface_resource,
struct wl_resource* parent_resource) {
struct sl_host_subcompositor* host = wl_resource_get_user_data(resource);
struct sl_host_surface* host_surface =
wl_resource_get_user_data(surface_resource);
struct sl_host_subcompositor* host =
static_cast<sl_host_subcompositor*>(wl_resource_get_user_data(resource));
struct sl_host_surface* host_surface = static_cast<sl_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);
static_cast<sl_host_surface*>(wl_resource_get_user_data(parent_resource));
struct sl_host_subsurface* host_subsurface = new sl_host_subsurface();
host_subsurface->ctx = host->ctx;
host_subsurface->resource =
@ -113,17 +87,18 @@ static void sl_subcompositor_get_subsurface(
host->proxy, host_surface->proxy, host_parent->proxy);
wl_subsurface_set_user_data(host_subsurface->proxy, host_subsurface);
host_surface->has_role = 1;
}
} // NOLINT(whitespace/indent)
static const struct wl_subcompositor_interface sl_subcompositor_implementation =
{sl_subcompositor_destroy, sl_subcompositor_get_subsurface};
static void sl_destroy_host_subcompositor(struct wl_resource* resource) {
struct sl_host_subcompositor* host = wl_resource_get_user_data(resource);
struct sl_host_subcompositor* host =
static_cast<sl_host_subcompositor*>(wl_resource_get_user_data(resource));
wl_subcompositor_destroy(host->proxy);
wl_resource_set_user_data(resource, NULL);
free(host);
delete host;
}
static void sl_bind_host_subcompositor(struct wl_client* client,
@ -131,19 +106,16 @@ static void sl_bind_host_subcompositor(struct wl_client* client,
uint32_t version,
uint32_t id) {
struct sl_context* ctx = (struct sl_context*)data;
struct sl_host_subcompositor* host;
host = malloc(sizeof(*host));
assert(host);
struct sl_host_subcompositor* host = new sl_host_subcompositor();
host->ctx = ctx;
host->resource =
wl_resource_create(client, &wl_subcompositor_interface, 1, id);
wl_resource_set_implementation(host->resource,
&sl_subcompositor_implementation, host,
sl_destroy_host_subcompositor);
host->proxy =
host->proxy = static_cast<wl_subcompositor*>(
wl_registry_bind(wl_display_get_registry(ctx->display),
ctx->subcompositor->id, &wl_subcompositor_interface, 1);
ctx->subcompositor->id, &wl_subcompositor_interface, 1));
wl_subcompositor_set_user_data(host->proxy, host);
}

View File

@ -1,332 +0,0 @@
// Copyright 2018 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "sommelier.h"
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#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);
}

View File

@ -0,0 +1,551 @@
// Copyright 2018 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "sommelier.h" // NOLINT(build/include_directory)
#include "sommelier-transform.h" // NOLINT(build/include_directory)
#include "weak-resource-ptr.h" // NOLINT(build/include_directory)
#include <algorithm>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include "text-input-extension-unstable-v1-client-protocol.h" // NOLINT(build/include_directory)
#include "text-input-extension-unstable-v1-server-protocol.h" // NOLINT(build/include_directory)
#include "text-input-unstable-v1-client-protocol.h" // NOLINT(build/include_directory)
#include "text-input-unstable-v1-server-protocol.h" // NOLINT(build/include_directory)
#include "text-input-x11-unstable-v1-server-protocol.h" // NOLINT(build/include_directory)
namespace {
// Versions supported by sommelier.
constexpr uint32_t kTextInputManagerVersion = 1;
constexpr uint32_t kTextInputExtensionVersion = 4;
constexpr uint32_t kTextInputX11Version = 1;
} // namespace
struct sl_host_text_input_manager {
struct sl_context* ctx;
struct wl_resource* resource;
struct zwp_text_input_manager_v1* proxy;
};
struct sl_host_text_input {
struct sl_context* ctx;
struct wl_resource* resource;
struct zwp_text_input_v1* proxy;
WeakResourcePtr<sl_host_surface> active_surface;
};
MAP_STRUCTS(zwp_text_input_v1, sl_host_text_input);
struct sl_host_text_input_extension {
struct sl_context* ctx;
struct wl_resource* resource;
struct zcr_text_input_extension_v1* proxy;
};
struct sl_host_extended_text_input {
struct sl_context* ctx;
struct wl_resource* resource;
struct sl_host_text_input* host_text_input;
struct zcr_extended_text_input_v1* proxy;
};
MAP_STRUCTS(zcr_extended_text_input_v1, sl_host_extended_text_input);
static void sl_text_input_activate(wl_client* client,
wl_resource* resource,
wl_resource* seat,
wl_resource* surface) {
struct sl_host_text_input* host =
static_cast<sl_host_text_input*>(wl_resource_get_user_data(resource));
struct sl_host_seat* host_seat =
static_cast<sl_host_seat*>(wl_resource_get_user_data(seat));
struct sl_host_surface* host_surface =
static_cast<sl_host_surface*>(wl_resource_get_user_data(surface));
host->active_surface = host_surface;
zwp_text_input_v1_activate(host->proxy, host_seat->proxy,
host_surface->proxy);
}
static void sl_text_input_deactivate(wl_client* client,
wl_resource* resource,
wl_resource* seat) {
struct sl_host_text_input* host =
static_cast<sl_host_text_input*>(wl_resource_get_user_data(resource));
struct sl_host_seat* host_seat =
static_cast<sl_host_seat*>(wl_resource_get_user_data(seat));
host->active_surface.Reset();
zwp_text_input_v1_deactivate(host->proxy, host_seat->proxy);
}
static void sl_text_input_set_cursor_rectangle(wl_client* client,
wl_resource* resource,
int32_t x,
int32_t y,
int32_t width,
int32_t height) {
struct sl_host_text_input* host =
static_cast<sl_host_text_input*>(wl_resource_get_user_data(resource));
if (!host->active_surface)
return;
int32_t x1 = x;
int32_t y1 = y;
int32_t x2 = x + width;
int32_t y2 = y + height;
sl_transform_guest_to_host(host->ctx, host->active_surface.get(), &x1, &y1);
sl_transform_guest_to_host(host->ctx, host->active_surface.get(), &x2, &y2);
zwp_text_input_v1_set_cursor_rectangle(host->proxy, x1, y1, x2 - x1, y2 - y1);
}
static const struct zwp_text_input_v1_interface sl_text_input_implementation = {
sl_text_input_activate,
sl_text_input_deactivate,
ForwardRequest<zwp_text_input_v1_show_input_panel>,
ForwardRequest<zwp_text_input_v1_hide_input_panel>,
ForwardRequest<zwp_text_input_v1_reset>,
ForwardRequest<zwp_text_input_v1_set_surrounding_text>,
ForwardRequest<zwp_text_input_v1_set_content_type>,
sl_text_input_set_cursor_rectangle,
ForwardRequest<zwp_text_input_v1_set_preferred_language>,
ForwardRequest<zwp_text_input_v1_commit_state>,
ForwardRequest<zwp_text_input_v1_invoke_action>,
};
static void sl_text_input_enter(void* data,
struct zwp_text_input_v1* text_input,
struct wl_surface* surface) {
// This is not currently used by cros_im. We can't simply forward the event
// as for an x11-hosted cros_im instance the text_input and wl_surface
// objects will be on different clients. We could add a corresponding event
// to text_input_x11 if needed.
}
static void sl_text_input_leave(void* data,
struct zwp_text_input_v1* text_input) {
struct sl_host_text_input* host = static_cast<sl_host_text_input*>(
zwp_text_input_v1_get_user_data(text_input));
zwp_text_input_v1_send_leave(host->resource);
}
static void sl_text_input_modifiers_map(void* data,
struct zwp_text_input_v1* text_input,
struct wl_array* map) {
struct sl_host_text_input* host = static_cast<sl_host_text_input*>(
zwp_text_input_v1_get_user_data(text_input));
zwp_text_input_v1_send_modifiers_map(host->resource, map);
}
static void sl_text_input_input_panel_state(
void* data, struct zwp_text_input_v1* text_input, uint32_t state) {
struct sl_host_text_input* host = static_cast<sl_host_text_input*>(
zwp_text_input_v1_get_user_data(text_input));
zwp_text_input_v1_send_input_panel_state(host->resource, state);
}
static void sl_text_input_preedit_string(void* data,
struct zwp_text_input_v1* text_input,
uint32_t serial,
const char* text,
const char* commit) {
struct sl_host_text_input* host = static_cast<sl_host_text_input*>(
zwp_text_input_v1_get_user_data(text_input));
zwp_text_input_v1_send_preedit_string(host->resource, serial, text, commit);
}
static void sl_text_input_preedit_styling(void* data,
struct zwp_text_input_v1* text_input,
uint32_t index,
uint32_t length,
uint32_t style) {
struct sl_host_text_input* host = static_cast<sl_host_text_input*>(
zwp_text_input_v1_get_user_data(text_input));
zwp_text_input_v1_send_preedit_styling(host->resource, index, length, style);
}
static void sl_text_input_preedit_cursor(void* data,
struct zwp_text_input_v1* text_input,
int32_t index) {
struct sl_host_text_input* host = static_cast<sl_host_text_input*>(
zwp_text_input_v1_get_user_data(text_input));
zwp_text_input_v1_send_preedit_cursor(host->resource, index);
}
static void sl_text_input_commit_string(void* data,
struct zwp_text_input_v1* text_input,
uint32_t serial,
const char* text) {
struct sl_host_text_input* host = static_cast<sl_host_text_input*>(
zwp_text_input_v1_get_user_data(text_input));
zwp_text_input_v1_send_commit_string(host->resource, serial, text);
}
static void sl_text_input_cursor_position(void* data,
struct zwp_text_input_v1* text_input,
int32_t index,
int32_t anchor) {
struct sl_host_text_input* host = static_cast<sl_host_text_input*>(
zwp_text_input_v1_get_user_data(text_input));
zwp_text_input_v1_send_cursor_position(host->resource, index, anchor);
}
static void sl_text_input_delete_surrounding_text(
void* data,
struct zwp_text_input_v1* text_input,
int32_t index,
uint32_t length) {
struct sl_host_text_input* host = static_cast<sl_host_text_input*>(
zwp_text_input_v1_get_user_data(text_input));
zwp_text_input_v1_send_delete_surrounding_text(host->resource, index, length);
}
static void sl_text_input_keysym(void* data,
struct zwp_text_input_v1* text_input,
uint32_t serial,
uint32_t time,
uint32_t sym,
uint32_t state,
uint32_t modifiers) {
struct sl_host_text_input* host = static_cast<sl_host_text_input*>(
zwp_text_input_v1_get_user_data(text_input));
zwp_text_input_v1_send_keysym(host->resource, serial, time, sym, state,
modifiers);
}
static void sl_text_input_language(void* data,
struct zwp_text_input_v1* text_input,
uint32_t serial,
const char* language) {
struct sl_host_text_input* host = static_cast<sl_host_text_input*>(
zwp_text_input_v1_get_user_data(text_input));
zwp_text_input_v1_send_language(host->resource, serial, language);
}
static void sl_text_input_text_direction(void* data,
struct zwp_text_input_v1* text_input,
uint32_t serial,
uint32_t direction) {
struct sl_host_text_input* host = static_cast<sl_host_text_input*>(
zwp_text_input_v1_get_user_data(text_input));
zwp_text_input_v1_send_text_direction(host->resource, serial, direction);
}
static const struct zwp_text_input_v1_listener sl_text_input_listener = {
sl_text_input_enter, sl_text_input_leave,
sl_text_input_modifiers_map, sl_text_input_input_panel_state,
sl_text_input_preedit_string, sl_text_input_preedit_styling,
sl_text_input_preedit_cursor, sl_text_input_commit_string,
sl_text_input_cursor_position, sl_text_input_delete_surrounding_text,
sl_text_input_keysym, sl_text_input_language,
sl_text_input_text_direction,
};
static void sl_destroy_host_text_input(struct wl_resource* resource) {
struct sl_host_text_input* host =
static_cast<sl_host_text_input*>(wl_resource_get_user_data(resource));
zwp_text_input_v1_destroy(host->proxy);
wl_resource_set_user_data(resource, NULL);
delete host;
}
static void sl_text_input_manager_create_text_input(
struct wl_client* client, struct wl_resource* resource, uint32_t id) {
struct sl_host_text_input_manager* host =
static_cast<sl_host_text_input_manager*>(
wl_resource_get_user_data(resource));
struct wl_resource* text_input_resource =
wl_resource_create(client, &zwp_text_input_v1_interface,
wl_resource_get_version(resource), id);
struct sl_host_text_input* text_input_host = new sl_host_text_input();
text_input_host->resource = text_input_resource;
text_input_host->ctx = host->ctx;
text_input_host->proxy =
zwp_text_input_manager_v1_create_text_input(host->proxy);
wl_resource_set_implementation(text_input_resource,
&sl_text_input_implementation, text_input_host,
sl_destroy_host_text_input);
zwp_text_input_v1_add_listener(text_input_host->proxy,
&sl_text_input_listener, text_input_host);
}
static void sl_destroy_host_text_input_manager(struct wl_resource* resource) {
struct sl_host_text_input_manager* host =
static_cast<sl_host_text_input_manager*>(
wl_resource_get_user_data(resource));
zwp_text_input_manager_v1_destroy(host->proxy);
wl_resource_set_user_data(resource, NULL);
delete host;
}
static struct zwp_text_input_manager_v1_interface
sl_text_input_manager_implementation = {
sl_text_input_manager_create_text_input,
};
static void sl_bind_host_text_input_manager(struct wl_client* client,
void* data,
uint32_t version,
uint32_t id) {
struct sl_context* ctx = (struct sl_context*)data;
struct sl_text_input_manager* text_input_manager = ctx->text_input_manager;
struct sl_host_text_input_manager* host = new sl_host_text_input_manager();
host->ctx = ctx;
host->resource =
wl_resource_create(client, &zwp_text_input_manager_v1_interface,
std::min(version, kTextInputManagerVersion), id);
wl_resource_set_implementation(host->resource,
&sl_text_input_manager_implementation, host,
sl_destroy_host_text_input_manager);
host->proxy = static_cast<zwp_text_input_manager_v1*>(wl_registry_bind(
wl_display_get_registry(ctx->display), text_input_manager->id,
&zwp_text_input_manager_v1_interface,
wl_resource_get_version(host->resource)));
zwp_text_input_manager_v1_set_user_data(host->proxy, host);
}
struct sl_global* sl_text_input_manager_global_create(struct sl_context* ctx) {
return sl_global_create(ctx, &zwp_text_input_manager_v1_interface,
kTextInputManagerVersion, ctx,
sl_bind_host_text_input_manager);
}
static void sl_extended_text_input_destroy(struct wl_client* client,
struct wl_resource* resource) {
wl_resource_destroy(resource);
}
static const struct zcr_extended_text_input_v1_interface
sl_extended_text_input_implementation = {
sl_extended_text_input_destroy,
ForwardRequest<zcr_extended_text_input_v1_set_input_type>,
ForwardRequest<
zcr_extended_text_input_v1_set_grammar_fragment_at_cursor>,
ForwardRequest<zcr_extended_text_input_v1_set_autocorrect_info>,
};
static void sl_extended_text_input_set_preedit_region(
void* data,
struct zcr_extended_text_input_v1* extended_text_input,
int32_t index,
uint32_t length) {
struct sl_host_extended_text_input* host =
static_cast<sl_host_extended_text_input*>(
zcr_extended_text_input_v1_get_user_data(extended_text_input));
zcr_extended_text_input_v1_send_set_preedit_region(host->resource, index,
length);
}
static void sl_extended_text_input_clear_grammar_fragments(
void* data,
struct zcr_extended_text_input_v1* extended_text_input,
uint32_t start,
uint32_t end) {
struct sl_host_extended_text_input* host =
static_cast<sl_host_extended_text_input*>(
zcr_extended_text_input_v1_get_user_data(extended_text_input));
zcr_extended_text_input_v1_send_clear_grammar_fragments(host->resource, start,
end);
}
static void sl_extended_text_input_add_grammar_fragment(
void* data,
struct zcr_extended_text_input_v1* extended_text_input,
uint32_t start,
uint32_t end,
const char* suggestion) {
struct sl_host_extended_text_input* host =
static_cast<sl_host_extended_text_input*>(
zcr_extended_text_input_v1_get_user_data(extended_text_input));
zcr_extended_text_input_v1_send_add_grammar_fragment(host->resource, start,
end, suggestion);
}
static void sl_extended_text_input_set_autocorrect_range(
void* data,
struct zcr_extended_text_input_v1* extended_text_input,
uint32_t start,
uint32_t end) {
struct sl_host_extended_text_input* host =
static_cast<sl_host_extended_text_input*>(
zcr_extended_text_input_v1_get_user_data(extended_text_input));
zcr_extended_text_input_v1_send_set_autocorrect_range(host->resource, start,
end);
}
static const struct zcr_extended_text_input_v1_listener
sl_extended_text_input_listener = {
sl_extended_text_input_set_preedit_region,
sl_extended_text_input_clear_grammar_fragments,
sl_extended_text_input_add_grammar_fragment,
sl_extended_text_input_set_autocorrect_range,
};
static void sl_destroy_host_extended_text_input(struct wl_resource* resource) {
struct sl_host_extended_text_input* host =
static_cast<sl_host_extended_text_input*>(
wl_resource_get_user_data(resource));
zcr_extended_text_input_v1_destroy(host->proxy);
wl_resource_set_user_data(resource, NULL);
delete host;
}
static void sl_text_input_extension_get_extended_text_input(
struct wl_client* client,
struct wl_resource* resource,
uint32_t id,
struct wl_resource* text_input) {
struct sl_host_text_input_extension* host =
static_cast<sl_host_text_input_extension*>(
wl_resource_get_user_data(resource));
struct sl_host_text_input* host_text_input =
static_cast<sl_host_text_input*>(wl_resource_get_user_data(text_input));
struct wl_resource* extended_text_input_resource =
wl_resource_create(client, &zcr_extended_text_input_v1_interface,
wl_resource_get_version(resource), id);
struct sl_host_extended_text_input* extended_text_input_host =
new sl_host_extended_text_input();
extended_text_input_host->resource = extended_text_input_resource;
extended_text_input_host->ctx = host->ctx;
extended_text_input_host->proxy =
zcr_text_input_extension_v1_get_extended_text_input(
host->proxy, host_text_input->proxy);
wl_resource_set_implementation(
extended_text_input_resource, &sl_extended_text_input_implementation,
extended_text_input_host, sl_destroy_host_extended_text_input);
zcr_extended_text_input_v1_add_listener(extended_text_input_host->proxy,
&sl_extended_text_input_listener,
extended_text_input_host);
} // NOLINT(whitespace/indent)
static void sl_destroy_host_text_input_extension(struct wl_resource* resource) {
struct sl_host_text_input_extension* host =
static_cast<sl_host_text_input_extension*>(
wl_resource_get_user_data(resource));
zcr_text_input_extension_v1_destroy(host->proxy);
wl_resource_set_user_data(resource, NULL);
delete host;
}
static struct zcr_text_input_extension_v1_interface
sl_text_input_extension_implementation = {
sl_text_input_extension_get_extended_text_input,
};
static void sl_bind_host_text_input_extension(struct wl_client* client,
void* data,
uint32_t version,
uint32_t id) {
struct sl_context* ctx = (struct sl_context*)data;
struct sl_text_input_extension* text_input_extension =
ctx->text_input_extension;
struct sl_host_text_input_extension* host =
new sl_host_text_input_extension();
host->ctx = ctx;
host->resource =
wl_resource_create(client, &zcr_text_input_extension_v1_interface,
std::min(version, kTextInputExtensionVersion), id);
wl_resource_set_implementation(host->resource,
&sl_text_input_extension_implementation, host,
sl_destroy_host_text_input_extension);
host->proxy = static_cast<zcr_text_input_extension_v1*>(wl_registry_bind(
wl_display_get_registry(ctx->display), text_input_extension->id,
&zcr_text_input_extension_v1_interface,
wl_resource_get_version(host->resource)));
zcr_text_input_extension_v1_set_user_data(host->proxy, host);
}
struct sl_global* sl_text_input_extension_global_create(
struct sl_context* ctx) {
return sl_global_create(ctx, &zcr_text_input_extension_v1_interface,
kTextInputExtensionVersion, ctx,
sl_bind_host_text_input_extension);
}
static void sl_text_input_x11_activate(wl_client* client,
wl_resource* resource,
wl_resource* text_input,
wl_resource* seat,
uint32_t x11_window_id) {
struct sl_host_text_input* host_text_input =
static_cast<sl_host_text_input*>(wl_resource_get_user_data(text_input));
struct sl_host_seat* host_seat =
static_cast<sl_host_seat*>(wl_resource_get_user_data(seat));
assert(host_text_input);
assert(host_seat);
struct sl_context* ctx = host_text_input->ctx;
struct sl_window* window;
wl_list_for_each(window, &ctx->windows, link) {
if (window->id != x11_window_id)
continue;
if (!window->host_surface_id)
return;
struct wl_resource* host_window_resource =
wl_client_get_object(ctx->client, window->host_surface_id);
if (!host_window_resource)
return;
sl_host_surface* host_surface = static_cast<sl_host_surface*>(
wl_resource_get_user_data(host_window_resource));
host_text_input->active_surface = host_surface;
zwp_text_input_v1_activate(host_text_input->proxy, host_seat->proxy,
host_surface->proxy);
return;
}
}
static const struct zcr_text_input_x11_v1_interface
sl_text_input_x11_implementation = {
sl_text_input_x11_activate,
};
static void sl_bind_host_text_input_x11(struct wl_client* client,
void* data,
uint32_t version,
uint32_t id) {
// This exists only between sommelier and its clients and there is no proxy
// to the host. For simplicity we don't use a sl_host_text_input_x11
// type as it is not needed.
wl_resource* resource =
wl_resource_create(client, &zcr_text_input_x11_v1_interface,
std::min(version, kTextInputX11Version), id);
wl_resource_set_implementation(resource, &sl_text_input_x11_implementation,
nullptr, nullptr);
}
struct sl_global* sl_text_input_x11_global_create(struct sl_context* ctx) {
return sl_global_create(ctx, &zcr_text_input_x11_v1_interface,
kTextInputX11Version, ctx,
sl_bind_host_text_input_x11);
}

View File

@ -0,0 +1,102 @@
// Copyright 2021 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "sommelier-timing.h" // NOLINT(build/include_directory)
#include <fstream>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <string>
#define NSEC_PER_SEC 1000000000
#define NSEC_PER_USEC 1000
static inline int64_t timespec_to_ns(timespec* t) {
return (int64_t)t->tv_sec * NSEC_PER_SEC + t->tv_nsec;
}
// Records start time to calculate first delta.
void Timing::RecordStartTime() {
clock_gettime(CLOCK_REALTIME, &last_event);
}
int64_t Timing::GetTime() {
timespec tp;
clock_gettime(CLOCK_REALTIME, &tp);
int64_t now = timespec_to_ns(&tp);
int64_t last = timespec_to_ns(&last_event);
last_event = tp;
return now - last;
}
// Create a new action, add info gained from attach call.
void Timing::UpdateLastAttach(int surface_id, int buffer_id) {
actions[event_id % kMaxNumActions] =
BufferAction(GetTime(), surface_id, buffer_id, BufferAction::ATTACH);
event_id++;
}
// Create a new action, add info gained from commit call.
void Timing::UpdateLastCommit(int surface_id) {
actions[event_id % kMaxNumActions] = BufferAction(
GetTime(), surface_id, kUnknownBufferId, BufferAction::COMMIT);
event_id++;
}
// Add a release action with release timing info.
void Timing::UpdateLastRelease(int buffer_id) {
actions[event_id % kMaxNumActions] = BufferAction(
GetTime(), kUnknownSurfaceId, buffer_id, BufferAction::RELEASE);
event_id++;
}
// Output the recorded actions to the timing log file.
void Timing::OutputLog() {
if (event_id == 0) {
std::cout << "No events in buffer, exiting" << std::endl;
return;
}
std::cout << "Writing buffer activity to the timing log file" << std::endl;
std::string output_filename =
std::string(filename) + "_set_" + std::to_string(saves);
std::ofstream outfile(output_filename);
int start = 0;
int buf_size = event_id;
if (event_id >= kMaxNumActions) {
start = event_id % kMaxNumActions;
buf_size = kMaxNumActions;
}
outfile << "Type Surface_ID Buffer_ID Delta_Time" << std::endl;
for (int i = 0; i < buf_size; i++) {
int idx = (i + start) % kMaxNumActions;
std::string type("?");
if (actions[idx].action_type == BufferAction::ATTACH) {
type = "a";
} else if (actions[idx].action_type == BufferAction::COMMIT) {
type = "c";
} else if (actions[idx].action_type == BufferAction::RELEASE) {
type = "r";
}
outfile << type << " ";
outfile << actions[idx].surface_id << " ";
outfile << actions[idx].buffer_id << " ";
outfile << static_cast<double>(actions[idx].delta_time) / NSEC_PER_USEC
<< std::endl;
}
std::stringstream nsec;
nsec << std::setw(9) << std::setfill('0') << last_event.tv_nsec;
outfile << "EndTime " << event_id - 1 << " " << last_event.tv_sec << "."
<< nsec.str() << std::endl;
outfile.close();
std::cout << "Finished writing " << output_filename << std::endl;
++saves;
}

View File

@ -0,0 +1,52 @@
// Copyright 2021 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef VM_TOOLS_SOMMELIER_SOMMELIER_TIMING_H_
#define VM_TOOLS_SOMMELIER_SOMMELIER_TIMING_H_
#include <stdint.h>
#include <time.h>
const int kUnknownBufferId = -1;
const int kUnknownSurfaceId = -1;
class Timing {
public:
explicit Timing(const char* fname) : filename(fname) {}
void RecordStartTime();
void UpdateLastAttach(int surface_id, int buffer_id);
void UpdateLastCommit(int surface_id);
void UpdateLastRelease(int buffer_id);
void OutputLog();
private:
// 10 min * 60 sec/min * 60 frames/sec * 3 actions/frame = 108000 actions
static const int kMaxNumActions = 10 * 60 * 60 * 3;
struct BufferAction {
enum Type { UNKNOWN, ATTACH, COMMIT, RELEASE };
int64_t delta_time;
int surface_id;
int buffer_id;
Type action_type;
BufferAction()
: surface_id(kUnknownSurfaceId),
buffer_id(kUnknownBufferId),
action_type(UNKNOWN) {}
explicit BufferAction(int64_t dt,
int sid = kUnknownSurfaceId,
int bid = kUnknownBufferId,
Type type = UNKNOWN)
: delta_time(dt), surface_id(sid), buffer_id(bid), action_type(type) {}
};
BufferAction actions[kMaxNumActions];
int event_id = 0;
int saves = 0;
const char* filename;
timespec last_event;
int64_t GetTime();
}; // class Timing
#endif // VM_TOOLS_SOMMELIER_SOMMELIER_TIMING_H_

View File

@ -0,0 +1,484 @@
// Copyright 2020 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "sommelier-tracing.h" // NOLINT(build/include_directory)
#include <assert.h>
#include <fcntl.h>
#include <memory>
#include <sstream>
#include <string>
#include <sys/stat.h>
#include <sys/types.h>
#include <time.h>
#include <vector>
#include <xcb/xproto.h>
#include "sommelier.h" // NOLINT(build/include_directory)
#include "sommelier-ctx.h" // NOLINT(build/include_directory)
#if defined(_M_IA64) || defined(_M_IX86) || defined(__ia64__) || \
defined(__i386__) || defined(__amd64__) || defined(__x86_64__) || \
defined(_M_AMD64)
#define HAS_RDTSC
#ifdef _MSC_VER
#include <intrin.h>
#else
#include <x86intrin.h>
#endif
#endif
#if defined(_M_ARM) || defined(_M_ARMT) || defined(__arm__) || \
defined(__thumb__) || defined(__aarch64__)
// TODO(jbates): support ARM CPU counter
#endif
#if defined(PERFETTO_TRACING)
PERFETTO_TRACK_EVENT_STATIC_STORAGE();
std::unique_ptr<perfetto::TracingSession> tracing_session;
void initialize_tracing(bool in_process_backend, bool system_backend) {
perfetto::TracingInitArgs args;
if (in_process_backend) {
args.backends |= perfetto::kInProcessBackend;
}
if (system_backend) {
args.backends |= perfetto::kSystemBackend;
}
perfetto::Tracing::Initialize(args);
perfetto::TrackEvent::Register();
}
void enable_tracing(bool create_session) {
perfetto::TraceConfig cfg;
cfg.add_buffers()->set_size_kb(1024); // Record up to 1 MiB.
auto* ds_cfg = cfg.add_data_sources()->mutable_config();
ds_cfg->set_name("track_event");
if (create_session) {
tracing_session = perfetto::Tracing::NewTrace();
tracing_session->Setup(cfg);
tracing_session->StartBlocking();
}
}
void dump_trace(const char* trace_filename) {
if (!trace_filename || !*trace_filename || !tracing_session) {
return;
}
std::vector<char> trace_data(tracing_session->ReadTraceBlocking());
// Write the trace into a file.
int fd = open(trace_filename, O_RDWR | O_CREAT | O_TRUNC, 0644);
if (fd == -1) {
fprintf(stderr, "error: unable to open trace file %s: %s\n", trace_filename,
strerror(errno));
return;
}
size_t pos = 0;
do {
ssize_t ret = write(fd, &trace_data[pos], trace_data.size() - pos);
if (ret < 0) {
if (errno == EINTR || errno == EAGAIN) {
continue;
}
fprintf(stderr, "error: unable to write trace file %s: %s\n",
trace_filename, strerror(errno));
close(fd);
return;
}
pos += ret;
} while (pos < trace_data.size());
close(fd);
}
// Returns NULL if atom is not recognized.
static const char* xcb_atom_to_string(uint32_t atom) {
switch (atom) {
case XCB_ATOM_NONE:
return "XCB_ATOM_NONE";
case XCB_ATOM_PRIMARY:
return "XCB_ATOM_PRIMARY";
case XCB_ATOM_SECONDARY:
return "XCB_ATOM_SECONDARY";
case XCB_ATOM_ARC:
return "XCB_ATOM_ARC";
case XCB_ATOM_ATOM:
return "XCB_ATOM_ATOM";
case XCB_ATOM_BITMAP:
return "XCB_ATOM_BITMAP";
case XCB_ATOM_CARDINAL:
return "XCB_ATOM_CARDINAL";
case XCB_ATOM_COLORMAP:
return "XCB_ATOM_COLORMAP";
case XCB_ATOM_CURSOR:
return "XCB_ATOM_CURSOR";
case XCB_ATOM_CUT_BUFFER0:
return "XCB_ATOM_CUT_BUFFER0";
case XCB_ATOM_CUT_BUFFER1:
return "XCB_ATOM_CUT_BUFFER1";
case XCB_ATOM_CUT_BUFFER2:
return "XCB_ATOM_CUT_BUFFER2";
case XCB_ATOM_CUT_BUFFER3:
return "XCB_ATOM_CUT_BUFFER3";
case XCB_ATOM_CUT_BUFFER4:
return "XCB_ATOM_CUT_BUFFER4";
case XCB_ATOM_CUT_BUFFER5:
return "XCB_ATOM_CUT_BUFFER5";
case XCB_ATOM_CUT_BUFFER6:
return "XCB_ATOM_CUT_BUFFER6";
case XCB_ATOM_CUT_BUFFER7:
return "XCB_ATOM_CUT_BUFFER7";
case XCB_ATOM_DRAWABLE:
return "XCB_ATOM_DRAWABLE";
case XCB_ATOM_FONT:
return "XCB_ATOM_FONT";
case XCB_ATOM_INTEGER:
return "XCB_ATOM_INTEGER";
case XCB_ATOM_PIXMAP:
return "XCB_ATOM_PIXMAP";
case XCB_ATOM_POINT:
return "XCB_ATOM_POINT";
case XCB_ATOM_RECTANGLE:
return "XCB_ATOM_RECTANGLE";
case XCB_ATOM_RESOURCE_MANAGER:
return "XCB_ATOM_RESOURCE_MANAGER";
case XCB_ATOM_RGB_COLOR_MAP:
return "XCB_ATOM_RGB_COLOR_MAP";
case XCB_ATOM_RGB_BEST_MAP:
return "XCB_ATOM_RGB_BEST_MAP";
case XCB_ATOM_RGB_BLUE_MAP:
return "XCB_ATOM_RGB_BLUE_MAP";
case XCB_ATOM_RGB_DEFAULT_MAP:
return "XCB_ATOM_RGB_DEFAULT_MAP";
case XCB_ATOM_RGB_GRAY_MAP:
return "XCB_ATOM_RGB_GRAY_MAP";
case XCB_ATOM_RGB_GREEN_MAP:
return "XCB_ATOM_RGB_GREEN_MAP";
case XCB_ATOM_RGB_RED_MAP:
return "XCB_ATOM_RGB_RED_MAP";
case XCB_ATOM_STRING:
return "XCB_ATOM_STRING";
case XCB_ATOM_VISUALID:
return "XCB_ATOM_VISUALID";
case XCB_ATOM_WINDOW:
return "XCB_ATOM_WINDOW";
case XCB_ATOM_WM_COMMAND:
return "XCB_ATOM_WM_COMMAND";
case XCB_ATOM_WM_HINTS:
return "XCB_ATOM_WM_HINTS";
case XCB_ATOM_WM_CLIENT_MACHINE:
return "XCB_ATOM_WM_CLIENT_MACHINE";
case XCB_ATOM_WM_ICON_NAME:
return "XCB_ATOM_WM_ICON_NAME";
case XCB_ATOM_WM_ICON_SIZE:
return "XCB_ATOM_WM_ICON_SIZE";
case XCB_ATOM_WM_NAME:
return "XCB_ATOM_WM_NAME";
case XCB_ATOM_WM_NORMAL_HINTS:
return "XCB_ATOM_WM_NORMAL_HINTS";
case XCB_ATOM_WM_SIZE_HINTS:
return "XCB_ATOM_WM_SIZE_HINTS";
case XCB_ATOM_WM_ZOOM_HINTS:
return "XCB_ATOM_WM_ZOOM_HINTS";
case XCB_ATOM_MIN_SPACE:
return "XCB_ATOM_MIN_SPACE";
case XCB_ATOM_NORM_SPACE:
return "XCB_ATOM_NORM_SPACE";
case XCB_ATOM_MAX_SPACE:
return "XCB_ATOM_MAX_SPACE";
case XCB_ATOM_END_SPACE:
return "XCB_ATOM_END_SPACE";
case XCB_ATOM_SUPERSCRIPT_X:
return "XCB_ATOM_SUPERSCRIPT_X";
case XCB_ATOM_SUPERSCRIPT_Y:
return "XCB_ATOM_SUPERSCRIPT_Y";
case XCB_ATOM_SUBSCRIPT_X:
return "XCB_ATOM_SUBSCRIPT_X";
case XCB_ATOM_SUBSCRIPT_Y:
return "XCB_ATOM_SUBSCRIPT_Y";
case XCB_ATOM_UNDERLINE_POSITION:
return "XCB_ATOM_UNDERLINE_POSITION";
case XCB_ATOM_UNDERLINE_THICKNESS:
return "XCB_ATOM_UNDERLINE_THICKNESS";
case XCB_ATOM_STRIKEOUT_ASCENT:
return "XCB_ATOM_STRIKEOUT_ASCENT";
case XCB_ATOM_STRIKEOUT_DESCENT:
return "XCB_ATOM_STRIKEOUT_DESCENT";
case XCB_ATOM_ITALIC_ANGLE:
return "XCB_ATOM_ITALIC_ANGLE";
case XCB_ATOM_X_HEIGHT:
return "XCB_ATOM_X_HEIGHT";
case XCB_ATOM_QUAD_WIDTH:
return "XCB_ATOM_QUAD_WIDTH";
case XCB_ATOM_WEIGHT:
return "XCB_ATOM_WEIGHT";
case XCB_ATOM_POINT_SIZE:
return "XCB_ATOM_POINT_SIZE";
case XCB_ATOM_RESOLUTION:
return "XCB_ATOM_RESOLUTION";
case XCB_ATOM_COPYRIGHT:
return "XCB_ATOM_COPYRIGHT";
case XCB_ATOM_NOTICE:
return "XCB_ATOM_NOTICE";
case XCB_ATOM_FONT_NAME:
return "XCB_ATOM_FONT_NAME";
case XCB_ATOM_FAMILY_NAME:
return "XCB_ATOM_FAMILY_NAME";
case XCB_ATOM_FULL_NAME:
return "XCB_ATOM_FULL_NAME";
case XCB_ATOM_CAP_HEIGHT:
return "XCB_ATOM_CAP_HEIGHT";
case XCB_ATOM_WM_CLASS:
return "XCB_ATOM_WM_CLASS";
case XCB_ATOM_WM_TRANSIENT_FOR:
return "XCB_ATOM_WM_TRANSIENT_FOR";
default:
return NULL;
}
}
// Annotate with the string representation of an atom.
//
// Supports well-known XCB atoms, and the fetched sl_context::atoms list. (To
// add an atom you're interested in debugging, modify |sl_context_atom_name|.)
void perfetto_annotate_atom(struct sl_context* ctx,
const perfetto::EventContext& perfetto,
const char* event_name,
xcb_atom_t atom) {
auto* dbg = perfetto.event()->add_debug_annotations();
dbg->set_name(event_name);
// Quickest option is to look up the XCB atoms, which have static values.
const char* atom_str = xcb_atom_to_string(atom);
if (atom_str) {
dbg->set_string_value(atom_str, strlen(atom_str));
return;
}
// Failing that, check if we've fetched this atom.
for (unsigned i = 0; i < ARRAY_SIZE(ctx->atoms); ++i) {
if (atom == ctx->atoms[i].value) {
const char* name = sl_context_atom_name(i);
if (name != nullptr) {
dbg->set_string_value(name, strlen(name));
return;
}
}
}
// If we reach here, we didn't find the atom name.
// We could ask the X server but that would require a round-trip.
std::string unknown("<unknown atom #");
unknown += std::to_string(atom);
unknown += '>';
dbg->set_string_value(unknown);
}
void perfetto_annotate_xcb_property_state(const perfetto::EventContext& event,
const char* name,
unsigned int state) {
auto* dbg = event.event()->add_debug_annotations();
dbg->set_name(name);
if (state == XCB_PROPERTY_NEW_VALUE) {
static const std::string prop_new("XCB_PROPERTY_NEW_VALUE");
dbg->set_string_value(prop_new);
} else if (state == XCB_PROPERTY_DELETE) {
static const std::string prop_delete("XCB_PROPERTY_DELETE");
dbg->set_string_value(prop_delete);
} else {
static const std::string unknown("<unknown>");
dbg->set_string_value(unknown);
}
}
// Annotate the given Perfetto EventContext with the name (if known) and the ID
// of the given window.
//
// Slow (iterates a linked list); only intended to be called if tracing is
// enabled.
void perfetto_annotate_window(struct sl_context* ctx,
const perfetto::EventContext& perfetto,
const char* event_name,
xcb_window_t window_id) {
auto* dbg = perfetto.event()->add_debug_annotations();
dbg->set_name(event_name);
struct sl_window* window = sl_lookup_window(ctx, window_id);
std::ostringstream value;
if (window != NULL && window->name != NULL) {
value << window->name << " <window #";
} else {
value << "<unknown window #";
}
// Always append the ID so we can track windows across their lifecycle.
value << window_id << '>';
dbg->set_string_value(value.str());
}
void perfetto_annotate_size_hints(const perfetto::EventContext& perfetto,
const sl_wm_size_hints& size_hints) {
auto* dbg = perfetto.event()->add_debug_annotations();
dbg->set_name("size_hints.flags");
std::string flags;
if (size_hints.flags & US_POSITION)
flags += "US_POSITION|";
if (size_hints.flags & US_SIZE)
flags += "US_SIZE|";
if (size_hints.flags & P_POSITION)
flags += "P_POSITION|";
if (size_hints.flags & P_SIZE)
flags += "P_SIZE|";
if (size_hints.flags & P_MIN_SIZE)
flags += "P_MIN_SIZE|";
if (size_hints.flags & P_MAX_SIZE)
flags += "P_MAX_SIZE|";
if (size_hints.flags & P_RESIZE_INC)
flags += "P_RESIZE_INC|";
if (size_hints.flags & P_ASPECT)
flags += "P_ASPECT|";
if (size_hints.flags & P_BASE_SIZE)
flags += "P_BASE_SIZE|";
if (size_hints.flags & P_WIN_GRAVITY)
flags += "P_WIN_GRAVITY|";
if (!flags.empty())
flags.pop_back(); // remove trailing '|'
dbg->set_string_value(flags);
dbg = perfetto.event()->add_debug_annotations();
dbg->set_name("size_hints.x");
dbg->set_int_value(size_hints.x);
dbg = perfetto.event()->add_debug_annotations();
dbg->set_name("size_hints.y");
dbg->set_int_value(size_hints.y);
dbg = perfetto.event()->add_debug_annotations();
dbg->set_name("size_hints.width");
dbg->set_int_value(size_hints.width);
dbg = perfetto.event()->add_debug_annotations();
dbg->set_name("size_hints.height");
dbg->set_int_value(size_hints.height);
dbg = perfetto.event()->add_debug_annotations();
dbg->set_name("size_hints.min_width");
dbg->set_int_value(size_hints.min_width);
dbg = perfetto.event()->add_debug_annotations();
dbg->set_name("size_hints.min_height");
dbg->set_int_value(size_hints.min_height);
dbg = perfetto.event()->add_debug_annotations();
dbg->set_name("size_hints.max_width");
dbg->set_int_value(size_hints.max_width);
dbg = perfetto.event()->add_debug_annotations();
dbg->set_name("size_hints.max_height");
dbg->set_int_value(size_hints.max_height);
dbg = perfetto.event()->add_debug_annotations();
dbg->set_name("size_hints.width_inc");
dbg->set_int_value(size_hints.width_inc);
dbg = perfetto.event()->add_debug_annotations();
dbg->set_name("size_hints.height_inc");
dbg->set_int_value(size_hints.height_inc);
dbg = perfetto.event()->add_debug_annotations();
dbg->set_name("size_hints.min_aspect.x");
dbg->set_int_value(size_hints.min_aspect.x);
dbg = perfetto.event()->add_debug_annotations();
dbg->set_name("size_hints.min_aspect.y");
dbg->set_int_value(size_hints.min_aspect.y);
dbg = perfetto.event()->add_debug_annotations();
dbg->set_name("size_hints.max_aspect.x");
dbg->set_int_value(size_hints.max_aspect.x);
dbg = perfetto.event()->add_debug_annotations();
dbg->set_name("size_hints.max_aspect.y");
dbg->set_int_value(size_hints.max_aspect.y);
dbg = perfetto.event()->add_debug_annotations();
dbg->set_name("size_hints.base_width");
dbg->set_int_value(size_hints.base_width);
dbg = perfetto.event()->add_debug_annotations();
dbg->set_name("size_hints.base_height");
dbg->set_int_value(size_hints.base_height);
dbg = perfetto.event()->add_debug_annotations();
dbg->set_name("size_hints.win_gravity");
dbg->set_int_value(size_hints.win_gravity);
}
// Add a Perfetto annotation for an X property storing a list of cardinals.
void perfetto_annotate_cardinal_list(const perfetto::EventContext& perfetto,
const char* event_name,
xcb_get_property_reply_t* reply) {
auto* dbg = perfetto.event()->add_debug_annotations();
dbg->set_name(event_name);
if (reply == nullptr) {
static const std::string null_str("<null>");
dbg->set_string_value(null_str);
return;
}
uint32_t length = xcb_get_property_value_length(reply);
if (length % sizeof(uint32_t) != 0) {
static const std::string invalid("<invalid>");
dbg->set_string_value(invalid);
return;
}
uint32_t* val = static_cast<uint32_t*>(xcb_get_property_value(reply));
uint32_t items = length / sizeof(uint32_t);
if (items == 0) {
static const std::string empty("<empty>");
dbg->set_string_value(empty);
return;
}
std::ostringstream str;
str << '[' << val[0];
for (uint32_t i = 1; i < items; ++i) {
str << ", " << val[i];
}
str << ']';
dbg->set_string_value(str.str());
}
static inline uint64_t get_cpu_ticks() {
#ifdef HAS_RDTSC
return __rdtsc();
#else
return 0;
#endif
}
static inline uint64_t get_timestamp_ns(clockid_t cid) {
struct timespec ts = {};
clock_gettime(cid, &ts);
return static_cast<uint64_t>(ts.tv_sec * 1000000000LL + ts.tv_nsec);
}
void perfetto_annotate_time_sync(const perfetto::EventContext& perfetto) {
uint64_t boot_time = get_timestamp_ns(CLOCK_BOOTTIME);
uint64_t cpu_time = get_cpu_ticks();
uint64_t monotonic_time = get_timestamp_ns(CLOCK_MONOTONIC);
// Read again to avoid cache miss overhead.
boot_time = get_timestamp_ns(CLOCK_BOOTTIME);
cpu_time = get_cpu_ticks();
monotonic_time = get_timestamp_ns(CLOCK_MONOTONIC);
auto* dbg = perfetto.event()->add_debug_annotations();
dbg->set_name("clock_sync_boottime");
dbg->set_uint_value(boot_time);
dbg = perfetto.event()->add_debug_annotations();
dbg->set_name("clock_sync_monotonic");
dbg->set_uint_value(monotonic_time);
dbg = perfetto.event()->add_debug_annotations();
dbg->set_name("clock_sync_cputime");
dbg->set_uint_value(cpu_time);
}
#else
// Stubs.
void initialize_tracing(bool in_process_backend, bool system_backend) {}
void enable_tracing(bool create_session) {}
void dump_trace(const char* trace_filename) {}
#endif // PERFETTO_TRACING

View File

@ -0,0 +1,53 @@
// Copyright 2020 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef VM_TOOLS_SOMMELIER_SOMMELIER_TRACING_H_
#define VM_TOOLS_SOMMELIER_SOMMELIER_TRACING_H_
#if defined(PERFETTO_TRACING)
#include <perfetto.h>
#include <xcb/xcb.h>
PERFETTO_DEFINE_CATEGORIES(
perfetto::Category("surface").SetDescription(
"Events for Wayland surface management"),
perfetto::Category("display").SetDescription("Events for Wayland display"),
perfetto::Category("drm").SetDescription("Events for Wayland drm"),
perfetto::Category("shell").SetDescription("Events for Wayland shell"),
perfetto::Category("shm").SetDescription(
"Events for Wayland shared memory"),
perfetto::Category("viewport")
.SetDescription("Events for Wayland viewport"),
perfetto::Category("sync").SetDescription("Events for Wayland sync points"),
perfetto::Category("x11wm").SetDescription(
"Events for X11 window management"),
perfetto::Category("gaming").SetDescription("Events for Gaming"),
perfetto::Category("other").SetDescription("Uncategorized Wayland calls."));
void perfetto_annotate_atom(struct sl_context* ctx,
const perfetto::EventContext& perfetto,
const char* event_name,
xcb_atom_t atom);
void perfetto_annotate_xcb_property_state(const perfetto::EventContext& event,
const char* name,
uint32_t state);
void perfetto_annotate_window(struct sl_context* ctx,
const perfetto::EventContext& perfetto,
const char* event_name,
xcb_window_t window_id);
void perfetto_annotate_size_hints(const perfetto::EventContext& perfetto,
const struct sl_wm_size_hints& size_hints);
void perfetto_annotate_cardinal_list(const perfetto::EventContext& perfetto,
const char* event_name,
xcb_get_property_reply_t* reply);
void perfetto_annotate_time_sync(const perfetto::EventContext& perfetto);
#else
#define TRACE_EVENT(category, name, ...)
#endif
void initialize_tracing(bool in_process_backend, bool system_backend);
void enable_tracing(bool create_session);
void dump_trace(char const* filename);
#endif // VM_TOOLS_SOMMELIER_SOMMELIER_TRACING_H_

View File

@ -0,0 +1,362 @@
// Copyright 2022 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <math.h>
#include "sommelier-tracing.h" // NOLINT(build/include_directory)
#include "sommelier-transform.h" // NOLINT(build/include_directory)
static void sl_transform_get_scale_factors(
struct sl_context* ctx,
const struct sl_host_surface* surface,
double* scalex,
double* scaley) {
if (ctx->use_direct_scale && surface && surface->has_own_scale) {
*scalex = surface->xdg_scale_x;
*scaley = surface->xdg_scale_y;
} else if (surface && surface->output) {
*scalex = surface->output.get()->xdg_scale_x;
*scaley = surface->output.get()->xdg_scale_y;
} else {
*scalex = ctx->xdg_scale_x;
*scaley = ctx->xdg_scale_y;
}
}
static double sl_transform_direct_axis_scale(struct sl_context* ctx,
struct sl_host_surface* surface,
uint32_t axis) {
double scalex, scaley;
sl_transform_get_scale_factors(ctx, surface, &scalex, &scaley);
return (axis == 0) ? scaley : scalex;
}
static void sl_transform_direct_to_host_damage(
struct sl_context* ctx,
const struct sl_host_surface* surface,
int64_t* x,
int64_t* y,
double scale_x,
double scale_y) {
double xwhole = trunc(static_cast<double>(*x) / scale_x);
double ywhole = trunc(static_cast<double>(*y) / scale_y);
*x = static_cast<int64_t>(xwhole);
*y = static_cast<int64_t>(ywhole);
}
static void sl_transform_direct_to_guest_fixed(struct sl_context* ctx,
struct sl_host_surface* surface,
wl_fixed_t* coord,
uint32_t axis) {
double scale = sl_transform_direct_axis_scale(ctx, surface, axis);
double result = wl_fixed_to_double(*coord) * scale;
*coord = wl_fixed_from_double(result);
}
static void sl_transform_direct_to_guest_fixed(struct sl_context* ctx,
struct sl_host_surface* surface,
wl_fixed_t* x,
wl_fixed_t* y) {
double scalex, scaley;
sl_transform_get_scale_factors(ctx, surface, &scalex, &scaley);
double resultx = wl_fixed_to_double(*x) * scalex;
double resulty = wl_fixed_to_double(*y) * scaley;
*x = wl_fixed_from_double(resultx);
*y = wl_fixed_from_double(resulty);
}
static void sl_transform_direct_to_host_fixed(struct sl_context* ctx,
struct sl_host_surface* surface,
wl_fixed_t* coord,
uint32_t axis) {
double scale = sl_transform_direct_axis_scale(ctx, surface, axis);
double result = wl_fixed_to_double(*coord) / scale;
*coord = wl_fixed_from_double(result);
}
static void sl_transform_direct_to_host_fixed(struct sl_context* ctx,
struct sl_host_surface* surface,
wl_fixed_t* x,
wl_fixed_t* y) {
double scalex, scaley;
sl_transform_get_scale_factors(ctx, surface, &scalex, &scaley);
double resultx = wl_fixed_to_double(*x) / scalex;
double resulty = wl_fixed_to_double(*y) / scaley;
*x = wl_fixed_from_double(resultx);
*y = wl_fixed_from_double(resulty);
}
static void sl_transform_direct_to_guest(struct sl_context* ctx,
struct sl_host_surface* surface,
int32_t* x,
int32_t* y) {
double scalex, scaley;
sl_transform_get_scale_factors(ctx, surface, &scalex, &scaley);
double inputx = scalex * static_cast<double>(*x);
double inputy = scaley * static_cast<double>(*y);
double xwhole =
(surface && surface->scale_round_on_x) ? lround(inputx) : trunc(inputx);
double ywhole =
(surface && surface->scale_round_on_y) ? lround(inputy) : trunc(inputy);
*x = static_cast<int32_t>(xwhole);
*y = static_cast<int32_t>(ywhole);
}
static void sl_transform_direct_to_host(struct sl_context* ctx,
struct sl_host_surface* surface,
int32_t* x,
int32_t* y) {
double scalex, scaley;
sl_transform_get_scale_factors(ctx, surface, &scalex, &scaley);
double xwhole = trunc(static_cast<double>(*x) / scalex);
double ywhole = trunc(static_cast<double>(*y) / scaley);
*x = static_cast<int32_t>(xwhole);
*y = static_cast<int32_t>(ywhole);
}
bool sl_transform_viewport_scale(struct sl_context* ctx,
struct sl_host_surface* surface,
double contents_scale,
int32_t* width,
int32_t* height) {
double scale = ctx->scale * contents_scale;
// TODO(mrisaacb): It may be beneficial to skip the set_destination call
// when the virtual and logical space match.
bool do_viewport = true;
if (ctx->use_direct_scale) {
sl_transform_direct_to_host(ctx, surface, width, height);
// For very small windows (in pixels), the resulting logical dimensions
// could be 0, which will cause issues with the viewporter interface.
//
// In these cases, fix it up here by forcing the logical output
// to be at least 1 pixel
if (*width <= 0)
*width = 1;
if (*height <= 0)
*height = 1;
} else {
*width = ceil(*width / scale);
*height = ceil(*height / scale);
}
return do_viewport;
}
void sl_transform_damage_coord(struct sl_context* ctx,
const struct sl_host_surface* surface,
double buffer_scalex,
double buffer_scaley,
int64_t* x1,
int64_t* y1,
int64_t* x2,
int64_t* y2) {
if (ctx->use_direct_scale) {
double scalex, scaley;
sl_transform_get_scale_factors(ctx, surface, &scalex, &scaley);
scalex *= buffer_scalex;
scaley *= buffer_scaley;
sl_transform_direct_to_host_damage(ctx, surface, x1, y1, scalex, scaley);
sl_transform_direct_to_host_damage(ctx, surface, x2, y2, scalex, scaley);
} else {
double sx = buffer_scalex * ctx->scale;
double sy = buffer_scaley * ctx->scale;
// Enclosing rect after scaling and outset by one pixel to account for
// potential filtering.
*x1 = MAX(MIN_SIZE, (*x1) - 1) / sx;
*y1 = MAX(MIN_SIZE, (*y1) - 1) / sy;
*x2 = ceil(MIN((*x2) + 1, MAX_SIZE) / sx);
*y2 = ceil(MIN((*y2) + 1, MAX_SIZE) / sy);
}
}
void sl_transform_host_to_guest(struct sl_context* ctx,
struct sl_host_surface* surface,
int32_t* x,
int32_t* y) {
if (ctx->use_direct_scale) {
sl_transform_direct_to_guest(ctx, surface, x, y);
} else {
(*x) *= ctx->scale;
(*y) *= ctx->scale;
}
}
void sl_transform_host_to_guest_fixed(struct sl_context* ctx,
struct sl_host_surface* surface,
wl_fixed_t* x,
wl_fixed_t* y) {
if (ctx->use_direct_scale) {
sl_transform_direct_to_guest_fixed(ctx, surface, x, y);
} else {
double dx = wl_fixed_to_double(*x);
double dy = wl_fixed_to_double(*y);
dx *= ctx->scale;
dy *= ctx->scale;
*x = wl_fixed_from_double(dx);
*y = wl_fixed_from_double(dy);
}
}
void sl_transform_host_to_guest_fixed(struct sl_context* ctx,
struct sl_host_surface* surface,
wl_fixed_t* coord,
uint32_t axis) {
if (ctx->use_direct_scale) {
sl_transform_direct_to_guest_fixed(ctx, surface, coord, axis);
} else {
double dx = wl_fixed_to_double(*coord);
dx *= ctx->scale;
*coord = wl_fixed_from_double(dx);
}
}
void sl_transform_guest_to_host(struct sl_context* ctx,
struct sl_host_surface* surface,
int32_t* x,
int32_t* y) {
if (ctx->use_direct_scale) {
sl_transform_direct_to_host(ctx, surface, x, y);
} else {
(*x) /= ctx->scale;
(*y) /= ctx->scale;
}
}
void sl_transform_guest_to_host_fixed(struct sl_context* ctx,
struct sl_host_surface* surface,
wl_fixed_t* x,
wl_fixed_t* y) {
if (ctx->use_direct_scale) {
sl_transform_direct_to_host_fixed(ctx, surface, x, y);
} else {
double dx = wl_fixed_to_double(*x);
double dy = wl_fixed_to_double(*y);
dx /= ctx->scale;
dy /= ctx->scale;
*x = wl_fixed_from_double(dx);
*y = wl_fixed_from_double(dy);
}
}
void sl_transform_guest_to_host_fixed(struct sl_context* ctx,
struct sl_host_surface* surface,
wl_fixed_t* coord,
uint32_t axis) {
if (ctx->use_direct_scale) {
sl_transform_direct_to_host_fixed(ctx, surface, coord, axis);
} else {
double dx = wl_fixed_to_double(*coord);
dx /= ctx->scale;
*coord = wl_fixed_from_double(dx);
}
}
void sl_transform_try_window_scale(struct sl_context* ctx,
struct sl_host_surface* surface,
int32_t width_in_pixels,
int32_t height_in_pixels) {
int32_t reverse_width = width_in_pixels;
int32_t reverse_height = height_in_pixels;
int32_t logical_width;
int32_t logical_height;
// This function should only have an effect in direct scale mode
if (!ctx->use_direct_scale)
return;
// Reset scale so that calls to sl_transform_get_scale_factors will not
// use the current scale.
sl_transform_reset_surface_scale(ctx, surface);
// Transform the window dimensions using the global/per-output scaling factors
sl_transform_guest_to_host(ctx, surface, &reverse_width, &reverse_height);
// Save the logical dimensions for later use
logical_width = reverse_width;
logical_height = reverse_height;
// Transform the logical dimensions back to the virtual pixel dimensions
sl_transform_host_to_guest(ctx, surface, &reverse_width, &reverse_height);
// If the computed logical width or height is zero, force the
// use of the global scaling factors
if ((reverse_width != width_in_pixels ||
reverse_height != height_in_pixels) &&
(logical_width > 0 && logical_height > 0)) {
// There is no match, let's override the scaling setting on our surface
surface->has_own_scale = 1;
surface->xdg_scale_x = static_cast<double>(width_in_pixels) /
static_cast<double>(logical_width);
surface->xdg_scale_y = static_cast<double>(height_in_pixels) /
static_cast<double>(logical_height);
surface->cached_logical_height = logical_height;
surface->cached_logical_width = logical_width;
// Try once more to do a full cycle (pixel -> logical -> pixel),
// if we aren't equal, we need to force a round up on the translation
// to the guest.
reverse_width = width_in_pixels;
reverse_height = height_in_pixels;
sl_transform_guest_to_host(ctx, surface, &reverse_width, &reverse_height);
sl_transform_host_to_guest(ctx, surface, &reverse_width, &reverse_height);
if (reverse_width != width_in_pixels)
surface->scale_round_on_x = true;
if (reverse_height != height_in_pixels)
surface->scale_round_on_y = true;
}
}
void sl_transform_reset_surface_scale(struct sl_context* ctx,
struct sl_host_surface* surface) {
surface->has_own_scale = 0;
surface->scale_round_on_x = surface->scale_round_on_y = false;
surface->xdg_scale_x = surface->xdg_scale_y = 0;
}
void sl_transform_output_dimensions(struct sl_context* ctx,
int32_t* width,
int32_t* height) {
*width = (*width) * ctx->scale;
*height = (*height) * ctx->scale;
}

View File

@ -0,0 +1,182 @@
// Copyright 2022 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef VM_TOOLS_SOMMELIER_SOMMELIER_TRANSFORM_H_
#define VM_TOOLS_SOMMELIER_SOMMELIER_TRANSFORM_H_
#include "sommelier.h" // NOLINT(build/include_directory)
#include "sommelier-ctx.h" // NOLINT(build/include_directory)
// Direct Scaling Mode Explained:
//
// It will be helpful to define the 3 coordinate spaces that we need to
// manage:
//
// 1. Physical Coordinate Space: This refers to the actual physical dimensions
// of the devices display. Typical sizes would be 3840x2160, 1920x1080, etc.
//
// 2. Virtual Coordinate Space: This refers to the coordinate space that is
// formed by multiplying the scale factor with the physical dimensions.
// (Example: scale = 1.0, physical = 3840x2160, virtual = 3840x2160)
// (Example: scale = 0.5, physical = 3840x2160, virtual = 1920x1080)
// The scale factor will come from the "--scale" command line parameter or
// from the associated environment variable.
//
// 3. Host Logical Space: The dimensions of this space are defined
// entirely by the host. The exact dimensions are retrieved through
// the xdg_output interface. It is assumed that there is a direct, linear
// relationship between the logical space and the physical space on the
// host. As an example:
// a) A 1600x900 logical space
// b) A 3840x2160 physical space
//
// If we place a 1600x900 dimensioned object at the origin of the logical
// space, it should appear as a 3840x2160 object within the physical space
// (also at the origin).
//
// The product of the desired scale factor and the physical dimensions may
// result in non-integer values. In these cases, the result
// is rounded down towards zero (truncate). This slight modification
// will require recomputation of the scale factors to maintain consistency
// between the two coordinate spaces. For this reason, the (single) scale
// factor provided as input from the user is used to generate the virtual
// coordinates. Then once those have been computed (and rounded), the scale
// factors for each axis will then be recalculated using the virtual and
// logical dimensions. Each axis is given its own scale factor because
// it is possible for only one axis to require rounding.
//
// The logical coordinates come to us from the host. This is the
// coordinate space that the host is operating in. This can change
// based on the users scale settings.
//
// The physical coordinate space is no longer necessary once the virtual
// coordinate space has been formed, so no scaling factors are needed to
// convert to that space.
//
// Xwayland operates within the virtual coordinate space and the
// host is operating within its logical space. Sommelier only needs to
// facilitate translations between these two coordinate spaces.
//
// The virtual to logical scale factors are derived from the ratios between
// the virtual coordinate spaces dimensions and the logical coordinate spaces
// dimensions.
//
// In this mode, a buffer that is full screen sized within Xwayland (virtual)
// will also be full screen sized in the logical coordinate space. The same
// pattern holds with a quarter resolution sized image. With a scale factor
// of 1.0, it is expected that there will be no scaling done to present the
// image onto the screen.
// Coordinate transform functions
//
// In general, the transformation functions fall under one of these
// two classes:
//
// 1. Transformations which follow the basic rules:
// A straight multiply for host->guest and straight divide for the opposite
// 2. Transformations which perform their transformations in a slightly
// different manner.
//
// The functions immediately following this block fall under the latter
// They are separate functions so these cases can be easily identified
// throughout the rest of the code.
//
// The functions that fall under the latter case work in the
// guest->host direction and do not have variants which work in the
// opposite direction.
// This particular function will return true if setting a destination
// viewport size is necessary. It can be false if the host/guest spaces
// matches.
// This is a potential optimization as it removes one step
// from the guest->host surface_attach cycle.
bool sl_transform_viewport_scale(struct sl_context* ctx,
struct sl_host_surface* surface,
double contents_scale,
int32_t* width,
int32_t* height);
void sl_transform_damage_coord(struct sl_context* ctx,
const struct sl_host_surface* surface,
double buffer_scalex,
double buffer_scaley,
int64_t* x1,
int64_t* y1,
int64_t* x2,
int64_t* y2);
// Basic Transformations
// The following transformations fall under the basic type
// 1D transformation functions have an axis specifier
// to indicate along which axis the transformation is to
// take place.
//
// The axis specifier will follow the wl_pointer::axis definitions
// 0 = vertical axis (Y)
// 1 = horizontal axis (X)
void sl_transform_host_to_guest(struct sl_context* ctx,
struct sl_host_surface* surface,
int32_t* x,
int32_t* y);
void sl_transform_host_to_guest_fixed(struct sl_context* ctx,
struct sl_host_surface* surface,
wl_fixed_t* x,
wl_fixed_t* y);
void sl_transform_host_to_guest_fixed(struct sl_context* ctx,
struct sl_host_surface* surface,
wl_fixed_t* coord,
uint32_t axis);
// Opposite Direction
void sl_transform_guest_to_host(struct sl_context* ctx,
struct sl_host_surface* surface,
int32_t* x,
int32_t* y);
void sl_transform_guest_to_host_fixed(struct sl_context* ctx,
struct sl_host_surface* surface,
wl_fixed_t* x,
wl_fixed_t* y);
void sl_transform_guest_to_host_fixed(struct sl_context* ctx,
struct sl_host_surface* surface,
wl_fixed_t* coord,
uint32_t axis);
// Given the desired window size in virtual pixels, this function
// will see if it can be cleanly converted to logical coordinates and back.
//
// If the desired dimensions can be met with the default scaling factors,
// no intervention will take place.
//
// If the desired dimensions CANNOT be met with the default scaling factors,
// a set of scaling factors will be chosen to match the nearest logical
// coordinates to the desired virtual pixel dimensions. These scaling factors
// will then be used for all transformations being performed on this surface.
//
// This function is a no-op when not in direct scale mode.
void sl_transform_try_window_scale(struct sl_context* ctx,
struct sl_host_surface* surface,
int32_t width_in_pixels,
int32_t height_in_pixels);
// Removes any custom scaling factors that have been set on the surface
// by try_window_scale
void sl_transform_reset_surface_scale(struct sl_context* ctx,
struct sl_host_surface* surface);
// This function performs the physical to virtual transformation
// based on the scale factor provided by the command line/env.
// This function is called in response to the physical dimensions being sent
// by the host. The virtual dimensions are calculated by this function and
// then relayed to the guest.
void sl_transform_output_dimensions(struct sl_context* ctx,
int32_t* width,
int32_t* height);
#endif // VM_TOOLS_SOMMELIER_SOMMELIER_TRANSFORM_H_

View File

@ -0,0 +1,32 @@
// Copyright 2021 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "sommelier-util.h" // NOLINT(build/include_directory)
#include <stdarg.h>
#include <stdio.h>
// Performs an asprintf operation and checks the result for validity and calls
// abort() if there's a failure. Returns a newly allocated string rather than
// taking a double pointer argument like asprintf.
__attribute__((__format__(__printf__, 1, 0))) char* sl_xasprintf(
const char* fmt, ...) {
char* str;
va_list args;
va_start(args, fmt);
int rv = vasprintf(&str, fmt, args);
assert(rv >= 0);
UNUSED(rv);
va_end(args);
return str;
}
#define DEFAULT_DELETER(TypeName, DeleteFunction) \
namespace std { \
void default_delete<TypeName>::operator()(TypeName* ptr) { \
DeleteFunction(ptr); \
} \
}
DEFAULT_DELETER(struct wl_event_source, wl_event_source_remove);

138
sommelier/sommelier-util.h Normal file
View File

@ -0,0 +1,138 @@
// Copyright 2021 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef VM_TOOLS_SOMMELIER_SOMMELIER_UTIL_H_
#define VM_TOOLS_SOMMELIER_SOMMELIER_UTIL_H_
#include <assert.h>
#include <cerrno>
#include <cstdio>
#include <cstring>
#include <memory>
#include <wayland-server.h>
#define errno_assert(rv) \
{ \
int macro_private_assert_value = (rv); \
if (!macro_private_assert_value) { \
fprintf(stderr, "Unexpected error: %s\n", strerror(errno)); \
assert(false); \
} \
}
#define UNUSED(x) ((void)(x))
// Performs an asprintf operation and checks the result for validity and calls
// abort() if there's a failure. Returns a newly allocated string rather than
// taking a double pointer argument like asprintf.
__attribute__((__format__(__printf__, 1, 0))) char* sl_xasprintf(
const char* fmt, ...);
#define DEFAULT_DELETER_FDECL(TypeName) \
namespace std { \
template <> \
struct default_delete<TypeName> { \
void operator()(TypeName* ptr); \
}; \
}
DEFAULT_DELETER_FDECL(struct wl_event_source);
// Maps wl_ to sl_ types, e.g. WlToSl<wl_seat>::type == sl_host_seat.
template <typename WaylandType>
struct WlToSl;
#define MAP_STRUCTS(WlType, SlType) \
template <> \
struct WlToSl<WlType> { \
using type = SlType; \
};
// Convert a request argument of type InArg to type OutArg. InArg is the type
// sommelier receives as a Wayland host. OutArg is the type used passed to the
// real host compositor. For Wayland resources, these will be wl_resource* and
// wl_..* (e.g. wl_surface*) respectively.
template <typename InArg, typename OutArg>
struct ConvertRequestArg {};
template <>
struct ConvertRequestArg<const char*, const char*> {
inline static const char* Convert(const char* arg) { return arg; }
};
template <>
struct ConvertRequestArg<uint32_t, uint32_t> {
inline static uint32_t Convert(uint32_t arg) { return arg; }
};
template <>
struct ConvertRequestArg<int32_t, int32_t> {
inline static int32_t Convert(int32_t arg) { return arg; }
};
template <typename OutArg>
struct ConvertRequestArg<wl_resource*, OutArg*> {
static OutArg* Convert(wl_resource* resource) {
if (!resource)
return nullptr;
using SlType = typename WlToSl<OutArg>::type;
SlType* host = static_cast<SlType*>(wl_resource_get_user_data(resource));
return host ? host->proxy : nullptr;
}
};
template <typename T>
inline bool IsNullWlResource(T arg) {
return false;
}
template <>
inline bool IsNullWlResource(wl_resource* resource) {
return resource == nullptr;
}
enum class AllowNullResource {
kNo = 0,
kYes = 1,
};
// Invoke the given wl_ function with each arg converted. This helper struct is
// so we can extract types from the wl_ function into a parameter pack for the
// fold expression and not require them to be explicitly written out.
template <typename... T>
struct ForwardRequestHelper;
template <typename... OutArgs>
struct ForwardRequestHelper<void (*)(OutArgs...)> {
template <auto wl_function, AllowNullResource allow_null, typename... InArgs>
static void Forward(struct wl_client* client, InArgs... args) {
if (allow_null == AllowNullResource::kNo) {
if ((IsNullWlResource<InArgs>(args) || ...)) {
fprintf(stderr, "error: received unexpected null resource in: %s\n",
__PRETTY_FUNCTION__);
return;
}
}
wl_function(ConvertRequestArg<InArgs, OutArgs>::Convert(args)...);
}
};
// Wraps the function which dispatches an request to the host for use as
// implementation for sommelier's implementation as a host. If null Wayland
// resources should be allowed, AllowNullResource::kYes should be set,
// otherwise the request will be considered invalid and dropped.
// Example usage:
// - ForwardRequest<wl_shell_surface_move>,
// - ForwardRequest<wl_shell_surface_set_fullscreen, AllowNullResource::kYes>
//
// The first argument (receiving object) is guaranteed by Wayland to be
// non-null but for code simplicity it is handled the same as the request
// arguments, with null being allowed or disallowed based on |allow_null|.
template <auto wl_function,
AllowNullResource allow_null = AllowNullResource::kNo,
typename... InArgs>
void ForwardRequest(InArgs... args) {
ForwardRequestHelper<decltype(wl_function)>::template Forward<wl_function,
allow_null>(
args...);
}
#endif // VM_TOOLS_SOMMELIER_SOMMELIER_UTIL_H_

View File

@ -1,14 +1,15 @@
// Copyright 2018 The Chromium OS Authors. All rights reserved.
// Copyright 2018 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "sommelier.h"
#include "sommelier.h" // NOLINT(build/include_directory)
#include "sommelier-tracing.h" // NOLINT(build/include_directory)
#include <assert.h>
#include <stdlib.h>
#include "viewporter-client-protocol.h"
#include "viewporter-server-protocol.h"
#include "viewporter-client-protocol.h" // NOLINT(build/include_directory)
#include "viewporter-server-protocol.h" // NOLINT(build/include_directory)
struct sl_host_viewporter {
struct sl_viewporter* viewporter;
@ -32,7 +33,8 @@ static void sl_viewport_set_source(struct wl_client* client,
wl_fixed_t y,
wl_fixed_t width,
wl_fixed_t height) {
struct sl_host_viewport* host = wl_resource_get_user_data(resource);
struct sl_host_viewport* host =
static_cast<sl_host_viewport*>(wl_resource_get_user_data(resource));
host->viewport.src_x = x;
host->viewport.src_y = y;
@ -44,7 +46,10 @@ static void sl_viewport_set_destination(struct wl_client* client,
struct wl_resource* resource,
int32_t width,
int32_t height) {
struct sl_host_viewport* host = wl_resource_get_user_data(resource);
TRACE_EVENT("viewport", "sl_viewport_set_destination", "resource_id",
wl_resource_get_id(resource));
struct sl_host_viewport* host =
static_cast<sl_host_viewport*>(wl_resource_get_user_data(resource));
host->viewport.dst_width = width;
host->viewport.dst_height = height;
@ -54,11 +59,12 @@ static const struct wp_viewport_interface sl_viewport_implementation = {
sl_viewport_destroy, sl_viewport_set_source, sl_viewport_set_destination};
static void sl_destroy_host_viewport(struct wl_resource* resource) {
struct sl_host_viewport* host = wl_resource_get_user_data(resource);
struct sl_host_viewport* host =
static_cast<sl_host_viewport*>(wl_resource_get_user_data(resource));
wl_resource_set_user_data(resource, NULL);
wl_list_remove(&host->viewport.link);
free(host);
delete host;
}
static void sl_viewporter_destroy(struct wl_client* client,
@ -70,12 +76,9 @@ static void sl_viewporter_get_viewport(struct wl_client* client,
struct wl_resource* resource,
uint32_t id,
struct wl_resource* surface_resource) {
struct sl_host_surface* host_surface =
wl_resource_get_user_data(surface_resource);
struct sl_host_viewport* host_viewport;
host_viewport = malloc(sizeof(*host_viewport));
assert(host_viewport);
struct sl_host_surface* host_surface = static_cast<sl_host_surface*>(
wl_resource_get_user_data(surface_resource));
struct sl_host_viewport* host_viewport = new sl_host_viewport();
host_viewport->viewport.src_x = -1;
host_viewport->viewport.src_y = -1;
@ -96,11 +99,12 @@ static const struct wp_viewporter_interface sl_viewporter_implementation = {
sl_viewporter_destroy, sl_viewporter_get_viewport};
static void sl_destroy_host_viewporter(struct wl_resource* resource) {
struct sl_host_viewporter* host = wl_resource_get_user_data(resource);
struct sl_host_viewporter* host =
static_cast<sl_host_viewporter*>(wl_resource_get_user_data(resource));
wp_viewporter_destroy(host->proxy);
wl_resource_set_user_data(resource, NULL);
free(host);
delete host;
}
static void sl_bind_host_viewporter(struct wl_client* client,
@ -108,17 +112,14 @@ static void sl_bind_host_viewporter(struct wl_client* client,
uint32_t version,
uint32_t id) {
struct sl_context* ctx = (struct sl_context*)data;
struct sl_host_viewporter* host;
host = malloc(sizeof(*host));
assert(host);
struct sl_host_viewporter* host = new sl_host_viewporter();
host->viewporter = ctx->viewporter;
host->resource = wl_resource_create(client, &wp_viewporter_interface, 1, id);
wl_resource_set_implementation(host->resource, &sl_viewporter_implementation,
host, sl_destroy_host_viewporter);
host->proxy =
host->proxy = static_cast<wp_viewporter*>(
wl_registry_bind(wl_display_get_registry(ctx->display),
ctx->viewporter->id, &wp_viewporter_interface, 1);
ctx->viewporter->id, &wp_viewporter_interface, 1));
wp_viewporter_set_user_data(host->proxy, host);
}

View File

@ -0,0 +1,254 @@
// Copyright 2021 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <cstddef>
#include <cstdint>
#include <dirent.h>
#include <sys/socket.h>
#include <unistd.h>
#include <fuzzer/FuzzedDataProvider.h>
#include <wayland-client.h>
#include "sommelier.h" // NOLINT(build/include_directory)
#include "sommelier-ctx.h" // NOLINT(build/include_directory)
#include "virtualization/wayland_channel.h" // NOLINT(build/include_directory)
class FuzzChannel : public WaylandChannel {
private:
int recv_fd = -1;
public:
int send_fd = -1;
~FuzzChannel() {
if (send_fd != -1) {
close(send_fd);
}
if (recv_fd != -1) {
close(recv_fd);
}
}
int32_t init() override { return 0; }
bool supports_dmabuf() override { return false; }
int32_t create_context(int& out_channel_fd) override {
int sv[2];
if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sv)) {
return -errno;
}
send_fd = sv[0];
recv_fd = sv[1];
out_channel_fd = sv[1];
return 0;
}
int32_t create_pipe(int& out_pipe_fd) override { return -1; }
int32_t send(const struct WaylandSendReceive& send) override { return 0; }
int32_t handle_channel_event(enum WaylandChannelEvent& event_type,
struct WaylandSendReceive& receive,
int& out_read_pipe) override {
uint8_t* buffer = static_cast<uint8_t*>(malloc(DEFAULT_BUFFER_SIZE));
int bytes = recv(receive.channel_fd, buffer, DEFAULT_BUFFER_SIZE, 0);
if (bytes < 0) {
free(buffer);
return -errno;
}
receive.data = buffer;
receive.data_size = bytes;
receive.num_fds = 0;
event_type = WaylandChannelEvent::Receive;
return 0;
}
int32_t allocate(const struct WaylandBufferCreateInfo& create_info,
struct WaylandBufferCreateOutput& create_output) override {
return -1;
}
int32_t sync(int dmabuf_fd, uint64_t flags) override { return 0; }
int32_t handle_pipe(int read_fd, bool readable, bool& hang_up) override {
return 0;
}
size_t max_send_size(void) override { return DEFAULT_BUFFER_SIZE; }
};
void null_logger(const char*, va_list) {}
class Environment {
public:
Environment() {
wl_log_set_handler_client(null_logger);
wl_log_set_handler_server(null_logger);
}
};
static int handle_host_to_sommelier_event(int fd, uint32_t mask, void* data) {
struct sl_context* ctx = (struct sl_context*)data;
int count = 0;
if ((mask & WL_EVENT_HANGUP) || (mask & WL_EVENT_ERROR)) {
return 0;
}
if (mask & WL_EVENT_READABLE)
count = wl_display_dispatch(ctx->display);
if (mask & WL_EVENT_WRITABLE)
wl_display_flush(ctx->display);
if (mask == 0) {
count = wl_display_dispatch_pending(ctx->display);
wl_display_flush(ctx->display);
}
return count;
}
int drain_socket(int fd, uint32_t mask, void* data) {
uint8_t buffer[DEFAULT_BUFFER_SIZE];
return recv(fd, buffer, DEFAULT_BUFFER_SIZE, 0) > 0;
}
// Count the number of open file descriptors to make sure we don't leak any.
// Aborts on error, as this should never fail.
int count_fds() {
DIR* dir = opendir("/proc/self/fd");
if (!dir) {
fprintf(stderr, "Failed to open /proc/self/fd: %m\n");
abort();
}
// Ignore the "." and ".." entries, along with the fd we just opened.
int count = -3;
// Needed to distinguish between eof and errors.
errno = 0;
while (struct dirent* dirent = readdir(dir)) {
count++;
}
if (errno) {
fprintf(stderr, "Failed to read from /proc/self/fd: %m\n");
abort();
}
if (closedir(dir)) {
fprintf(stderr, "Failed to close /proc/self/fd: %m\n");
abort();
}
return count;
}
int LLVMFuzzerTestOneInput_real(const uint8_t* data, size_t size) {
static Environment env;
FuzzedDataProvider source(data, size);
struct sl_context ctx;
FuzzChannel channel;
int host_to_sommelier, client_to_sommelier;
sl_context_init_default(&ctx);
// Create a connection from the host to sommelier. This is done via a
// WaylandChannel implementation that keeps a socketpair internally. One end
// goes to sommelier to listen on/write to, the other end is kept by us. The
// channel implements send with a no-op, so we don't ever have to read from
// our end.
ctx.host_display = wl_display_create();
struct wl_event_loop* event_loop =
wl_display_get_event_loop(ctx.host_display);
ctx.channel = &channel;
sl_context_init_wayland_channel(&ctx, event_loop, /*display=*/false);
// `display` takes ownership of `virtwl_display_fd`
ctx.display = wl_display_connect_to_fd(ctx.virtwl_display_fd);
// Create a connection from the client to sommelier. One end is passed into
// libwayland for sommelier to handle, the other end is owned by the
// fuzzer. We set up the event loop to drain any data send by sommelier to our
// end, and write fuzz data to our end in the main loop.
int sv[2];
int ret = socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sv);
assert(!ret);
// wl_client takes ownership of its file descriptor
ctx.client = wl_client_create(ctx.host_display, sv[0]);
std::unique_ptr<struct wl_event_source> drain_event(wl_event_loop_add_fd(
event_loop, sv[1], WL_EVENT_READABLE, drain_socket, nullptr));
// Add the host-to-sommelier display to the event loop. The client wayland
// library doesn't have an event loop system, instead you're expected to
// integrate into another event loop using wl_display_get_fd if you need to.
std::unique_ptr<struct wl_event_source> display_event(
wl_event_loop_add_fd(event_loop, wl_display_get_fd(ctx.display),
WL_EVENT_READABLE | WL_EVENT_WRITABLE,
handle_host_to_sommelier_event, &ctx));
// Getting the registry object from the host and listening for events is the
// top-level task in sommelier. Everything else is driven by messages from the
// host or the client.
auto* registry = wl_display_get_registry(ctx.display);
wl_registry_add_listener(registry, &sl_registry_listener, &ctx);
host_to_sommelier = channel.send_fd;
client_to_sommelier = sv[1];
while (source.remaining_bytes() &&
!wl_list_empty(wl_display_get_client_list(ctx.host_display)) &&
!wl_display_get_error(ctx.display)) {
// Randomly pick a connection to write too.
int fd = source.ConsumeBool() ? host_to_sommelier : client_to_sommelier;
// The random length string extractor uses a terminator character to prevent
// the boundaries of a message from shifting when the fuzzer inserts or
// removed bytes. This is (probably) better then consuming a length and then
// consuming that many characters.
std::string data = source.ConsumeRandomLengthString(DEFAULT_BUFFER_SIZE);
int sent = send(fd, data.data(), data.length(), 0);
assert(sent == data.length());
wl_display_flush_clients(ctx.host_display);
wl_event_loop_dispatch(event_loop, 0);
}
wl_registry_destroy(registry);
close(ctx.virtwl_socket_fd);
close(client_to_sommelier);
ctx.wayland_channel_event_source.reset();
ctx.virtwl_socket_event_source.reset();
drain_event.reset();
display_event.reset();
wl_display_destroy_clients(ctx.host_display);
wl_display_destroy(ctx.host_display);
wl_display_disconnect(ctx.display);
return 0;
}
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
int start_fds = count_fds();
int ret = LLVMFuzzerTestOneInput_real(data, size);
int end_fds = count_fds();
if (start_fds != end_fds) {
fprintf(stderr, "Leaked %d file descriptors!\n", end_fds - start_fds);
abort();
}
return ret;
}

View File

@ -0,0 +1,593 @@
// Copyright 2021 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "sommelier-window.h" // NOLINT(build/include_directory)
#include <assert.h>
#include "sommelier.h" // NOLINT(build/include_directory)
#include "sommelier-tracing.h" // NOLINT(build/include_directory)
#include "sommelier-transform.h" // NOLINT(build/include_directory)
#include "aura-shell-client-protocol.h" // NOLINT(build/include_directory)
#include "xdg-shell-client-protocol.h" // NOLINT(build/include_directory)
#define APPLICATION_ID_FORMAT_PREFIX "org.chromium.guest_os.%s"
#define XID_APPLICATION_ID_FORMAT APPLICATION_ID_FORMAT_PREFIX ".xid.%d"
#define WM_CLIENT_LEADER_APPLICATION_ID_FORMAT \
APPLICATION_ID_FORMAT_PREFIX ".wmclientleader.%d"
#define WM_CLASS_APPLICATION_ID_FORMAT \
APPLICATION_ID_FORMAT_PREFIX ".wmclass.%s"
#define X11_PROPERTY_APPLICATION_ID_FORMAT \
APPLICATION_ID_FORMAT_PREFIX ".xprop.%s"
sl_window::sl_window(struct sl_context* ctx,
xcb_window_t id,
int x,
int y,
int width,
int height,
int border_width)
: ctx(ctx),
id(id),
x(x),
y(y),
width(width),
height(height),
border_width(border_width) {
wl_list_insert(&ctx->unpaired_windows, &link);
pixman_region32_init(&shape_rectangles);
}
sl_window::~sl_window() {
if (this == ctx->host_focus_window) {
ctx->host_focus_window = nullptr;
ctx->needs_set_input_focus = 1;
}
free(name);
free(clazz);
free(startup_id);
wl_list_remove(&link);
pixman_region32_fini(&shape_rectangles);
}
void sl_configure_window(struct sl_window* window) {
TRACE_EVENT("surface", "sl_configure_window", "id", window->id);
assert(!window->pending_config.serial);
if (window->next_config.mask) {
int values[5];
int x = window->x;
int y = window->y;
int i = 0;
xcb_configure_window(window->ctx->connection, window->frame_id,
window->next_config.mask, window->next_config.values);
if (window->next_config.mask & XCB_CONFIG_WINDOW_X)
x = window->next_config.values[i++];
if (window->next_config.mask & XCB_CONFIG_WINDOW_Y)
y = window->next_config.values[i++];
if (window->next_config.mask & XCB_CONFIG_WINDOW_WIDTH)
window->width = window->next_config.values[i++];
if (window->next_config.mask & XCB_CONFIG_WINDOW_HEIGHT)
window->height = window->next_config.values[i++];
if (window->next_config.mask & XCB_CONFIG_WINDOW_BORDER_WIDTH)
window->border_width = window->next_config.values[i++];
// Set x/y to origin in case window gravity is not northwest as expected.
assert(window->managed);
values[0] = 0;
values[1] = 0;
values[2] = window->width;
values[3] = window->height;
values[4] = window->border_width;
xcb_configure_window(
window->ctx->connection, window->id,
XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_WIDTH |
XCB_CONFIG_WINDOW_HEIGHT | XCB_CONFIG_WINDOW_BORDER_WIDTH,
values);
if (x != window->x || y != window->y) {
window->x = x;
window->y = y;
sl_send_configure_notify(window);
}
}
if (window->managed) {
xcb_change_property(window->ctx->connection, XCB_PROP_MODE_REPLACE,
window->id, window->ctx->atoms[ATOM_NET_WM_STATE].value,
XCB_ATOM_ATOM, 32, window->next_config.states_length,
window->next_config.states);
}
window->pending_config = window->next_config;
window->next_config.serial = 0;
window->next_config.mask = 0;
window->next_config.states_length = 0;
}
void sl_send_configure_notify(struct sl_window* window) {
xcb_configure_notify_event_t event = {};
event.response_type = XCB_CONFIGURE_NOTIFY;
event.pad0 = 0;
event.event = window->id;
event.window = window->id;
event.above_sibling = XCB_WINDOW_NONE;
event.x = static_cast<int16_t>(window->x);
event.y = static_cast<int16_t>(window->y);
event.width = static_cast<uint16_t>(window->width);
event.height = static_cast<uint16_t>(window->height);
event.border_width = static_cast<uint16_t>(window->border_width);
event.override_redirect = 0;
event.pad1 = 0;
xcb_send_event(window->ctx->connection, 0, window->id,
XCB_EVENT_MASK_STRUCTURE_NOTIFY,
reinterpret_cast<char*>(&event));
}
int sl_process_pending_configure_acks(struct sl_window* window,
struct sl_host_surface* host_surface) {
if (!window->pending_config.serial)
return 0;
#ifdef COMMIT_LOOP_FIX
// Do not commit/ack if there is nothing to change.
//
// TODO(b/181077580): we should never do this, but avoiding it requires a
// more systemic fix
if (!window->pending_config.mask && window->pending_config.states_length == 0)
return 0;
#endif
if (window->managed && host_surface) {
uint32_t width = window->width + window->border_width * 2;
uint32_t height = window->height + window->border_width * 2;
// Early out if we expect contents to match window size at some point in
// the future.
if (width != host_surface->contents_width ||
height != host_surface->contents_height) {
return 0;
}
}
if (window->xdg_surface) {
xdg_surface_ack_configure(window->xdg_surface,
window->pending_config.serial);
}
window->pending_config.serial = 0;
if (window->next_config.serial)
sl_configure_window(window);
return 1;
}
void sl_commit(struct sl_window* window, struct sl_host_surface* host_surface) {
if (sl_process_pending_configure_acks(window, host_surface)) {
if (host_surface)
wl_surface_commit(host_surface->proxy);
}
}
static void sl_internal_xdg_popup_configure(void* data,
struct xdg_popup* xdg_popup,
int32_t x,
int32_t y,
int32_t width,
int32_t height) {}
static void sl_internal_xdg_popup_done(void* data,
struct xdg_popup* xdg_popup) {}
static const struct xdg_popup_listener sl_internal_xdg_popup_listener = {
sl_internal_xdg_popup_configure, sl_internal_xdg_popup_done};
static void sl_internal_xdg_surface_configure(void* data,
struct xdg_surface* xdg_surface,
uint32_t serial) {
TRACE_EVENT("surface", "sl_internal_xdg_surface_configure");
struct sl_window* window =
static_cast<sl_window*>(xdg_surface_get_user_data(xdg_surface));
window->next_config.serial = serial;
if (!window->pending_config.serial) {
struct wl_resource* host_resource;
struct sl_host_surface* host_surface = NULL;
host_resource =
wl_client_get_object(window->ctx->client, window->host_surface_id);
if (host_resource)
host_surface = static_cast<sl_host_surface*>(
wl_resource_get_user_data(host_resource));
sl_configure_window(window);
sl_commit(window, host_surface);
}
}
static const struct xdg_surface_listener sl_internal_xdg_surface_listener = {
sl_internal_xdg_surface_configure};
static void sl_internal_xdg_toplevel_configure(
void* data,
struct xdg_toplevel* xdg_toplevel,
int32_t width,
int32_t height,
struct wl_array* states) {
TRACE_EVENT("other", "sl_internal_xdg_toplevel_configure");
struct sl_window* window =
static_cast<sl_window*>(xdg_toplevel_get_user_data(xdg_toplevel));
int activated = 0;
uint32_t* state;
int i = 0;
if (!window->managed)
return;
if (width && height) {
int32_t width_in_pixels = width;
int32_t height_in_pixels = height;
int i = 0;
// We are receiving a request to resize a window (in logical dimensions)
// If the request is equal to the cached values we used to make adjustments
// do not recalculate the values
// However, if the request is not equal to the cached values, try
// and keep the buffer same size as what was previously set
// by the application.
struct sl_host_surface* paired_surface = window->paired_surface;
if (paired_surface && paired_surface->has_own_scale) {
if (width != paired_surface->cached_logical_width ||
height != paired_surface->cached_logical_height) {
sl_transform_try_window_scale(window->ctx, paired_surface,
window->width, window->height);
}
}
sl_transform_host_to_guest(window->ctx, window->paired_surface,
&width_in_pixels, &height_in_pixels);
window->next_config.mask = XCB_CONFIG_WINDOW_WIDTH |
XCB_CONFIG_WINDOW_HEIGHT |
XCB_CONFIG_WINDOW_BORDER_WIDTH;
if (!(window->size_flags & (US_POSITION | P_POSITION))) {
window->next_config.mask |= XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y;
if (window->paired_surface && window->paired_surface->output) {
window->next_config.values[i++] =
window->paired_surface->output->virt_x +
(window->paired_surface->output->width - width_in_pixels) / 2;
window->next_config.values[i++] =
window->paired_surface->output->virt_y +
(window->paired_surface->output->height - height_in_pixels) / 2;
} else {
window->next_config.values[i++] =
window->ctx->screen->width_in_pixels / 2 - width_in_pixels / 2;
window->next_config.values[i++] =
window->ctx->screen->height_in_pixels / 2 - height_in_pixels / 2;
}
}
window->next_config.values[i++] = width_in_pixels;
window->next_config.values[i++] = height_in_pixels;
window->next_config.values[i++] = 0;
}
window->allow_resize = 1;
window->compositor_fullscreen = 0;
sl_array_for_each(state, states) {
if (*state == XDG_TOPLEVEL_STATE_FULLSCREEN) {
window->allow_resize = 0;
window->next_config.states[i++] =
window->ctx->atoms[ATOM_NET_WM_STATE_FULLSCREEN].value;
window->compositor_fullscreen = 1;
}
if (*state == XDG_TOPLEVEL_STATE_MAXIMIZED) {
window->allow_resize = 0;
window->next_config.states[i++] =
window->ctx->atoms[ATOM_NET_WM_STATE_MAXIMIZED_VERT].value;
window->next_config.states[i++] =
window->ctx->atoms[ATOM_NET_WM_STATE_MAXIMIZED_HORZ].value;
}
if (*state == XDG_TOPLEVEL_STATE_ACTIVATED) {
activated = 1;
window->next_config.states[i++] =
window->ctx->atoms[ATOM_NET_WM_STATE_FOCUSED].value;
}
if (*state == XDG_TOPLEVEL_STATE_RESIZING)
window->allow_resize = 0;
}
if (activated != window->activated) {
if (activated != (window->ctx->host_focus_window == window)) {
window->ctx->host_focus_window = activated ? window : NULL;
window->ctx->needs_set_input_focus = 1;
}
window->activated = activated;
}
window->next_config.states_length = i;
}
static void sl_internal_xdg_toplevel_close(void* data,
struct xdg_toplevel* xdg_toplevel) {
TRACE_EVENT("other", "sl_internal_xdg_toplevel_close");
struct sl_window* window =
static_cast<sl_window*>(xdg_toplevel_get_user_data(xdg_toplevel));
xcb_client_message_event_t event = {};
event.response_type = XCB_CLIENT_MESSAGE;
event.format = 32;
event.window = window->id;
event.type = window->ctx->atoms[ATOM_WM_PROTOCOLS].value;
event.data.data32[0] = window->ctx->atoms[ATOM_WM_DELETE_WINDOW].value;
event.data.data32[1] = XCB_CURRENT_TIME;
xcb_send_event(window->ctx->connection, 0, window->id,
XCB_EVENT_MASK_NO_EVENT, (const char*)&event);
}
static const struct xdg_toplevel_listener sl_internal_xdg_toplevel_listener = {
sl_internal_xdg_toplevel_configure, sl_internal_xdg_toplevel_close};
void sl_update_application_id(struct sl_context* ctx,
struct sl_window* window) {
TRACE_EVENT("other", "sl_update_application_id");
if (!window->aura_surface)
return;
if (ctx->application_id) {
zaura_surface_set_application_id(window->aura_surface, ctx->application_id);
return;
}
// Don't set application id for X11 override redirect. This prevents
// aura shell from thinking that these are regular application windows
// that should appear in application lists.
if (!ctx->xwayland || window->managed) {
char* application_id_str;
if (!window->app_id_property.empty()) {
application_id_str =
sl_xasprintf(X11_PROPERTY_APPLICATION_ID_FORMAT, ctx->vm_id,
window->app_id_property.c_str());
} else if (window->clazz) {
application_id_str = sl_xasprintf(WM_CLASS_APPLICATION_ID_FORMAT,
ctx->vm_id, window->clazz);
} else if (window->client_leader != XCB_WINDOW_NONE) {
application_id_str = sl_xasprintf(WM_CLIENT_LEADER_APPLICATION_ID_FORMAT,
ctx->vm_id, window->client_leader);
} else {
application_id_str =
sl_xasprintf(XID_APPLICATION_ID_FORMAT, ctx->vm_id, window->id);
}
zaura_surface_set_application_id(window->aura_surface, application_id_str);
free(application_id_str);
}
}
void sl_window_update(struct sl_window* window) {
TRACE_EVENT("surface", "sl_window_update", "id", window->id);
struct wl_resource* host_resource = NULL;
struct sl_host_surface* host_surface;
struct sl_context* ctx = window->ctx;
struct sl_window* parent = NULL;
if (window->host_surface_id) {
host_resource = wl_client_get_object(ctx->client, window->host_surface_id);
if (host_resource && window->unpaired) {
wl_list_remove(&window->link);
wl_list_insert(&ctx->windows, &window->link);
window->unpaired = 0;
}
} else if (!window->unpaired) {
wl_list_remove(&window->link);
wl_list_insert(&ctx->unpaired_windows, &window->link);
window->unpaired = 1;
window->paired_surface = NULL;
}
if (!host_resource) {
if (window->aura_surface) {
zaura_surface_destroy(window->aura_surface);
window->aura_surface = NULL;
}
if (window->xdg_toplevel) {
xdg_toplevel_destroy(window->xdg_toplevel);
window->xdg_toplevel = NULL;
}
if (window->xdg_popup) {
xdg_popup_destroy(window->xdg_popup);
window->xdg_popup = NULL;
}
if (window->xdg_surface) {
xdg_surface_destroy(window->xdg_surface);
window->xdg_surface = NULL;
}
window->realized = 0;
return;
}
host_surface =
static_cast<sl_host_surface*>(wl_resource_get_user_data(host_resource));
assert(host_surface);
assert(!host_surface->has_role);
if (!window->unpaired) {
window->paired_surface = host_surface;
sl_transform_try_window_scale(ctx, host_surface, window->width,
window->height);
}
assert(ctx->xdg_shell);
assert(ctx->xdg_shell->internal);
if (window->managed) {
if (window->transient_for != XCB_WINDOW_NONE) {
struct sl_window* sibling;
wl_list_for_each(sibling, &ctx->windows, link) {
if (sibling->id == window->transient_for) {
if (sibling->xdg_toplevel)
parent = sibling;
break;
}
}
}
}
// If we have a transient parent, but could not find it in the list of
// realized windows, then pick the window that had the last event for the
// parent. We update this again when we gain focus, so if we picked the wrong
// one it can get corrected at that point (but it's also possible the parent
// will never be realized, which is why selecting one here is important).
if (!window->managed ||
(!parent && window->transient_for != XCB_WINDOW_NONE)) {
struct sl_window* sibling;
uint32_t parent_last_event_serial = 0;
wl_list_for_each(sibling, &ctx->windows, link) {
struct wl_resource* sibling_host_resource;
struct sl_host_surface* sibling_host_surface;
if (!sibling->realized)
continue;
sibling_host_resource =
wl_client_get_object(ctx->client, sibling->host_surface_id);
if (!sibling_host_resource)
continue;
// Any parent will do but prefer last event window.
sibling_host_surface = static_cast<sl_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 = xdg_wm_base_get_xdg_surface(ctx->xdg_shell->internal,
host_surface->proxy);
xdg_surface_add_listener(window->xdg_surface,
&sl_internal_xdg_surface_listener, window);
}
if (ctx->aura_shell) {
uint32_t frame_color;
if (!window->aura_surface) {
window->aura_surface = zaura_shell_get_aura_surface(
ctx->aura_shell->internal, host_surface->proxy);
}
zaura_surface_set_frame(window->aura_surface,
window->decorated
? ZAURA_SURFACE_FRAME_TYPE_NORMAL
: window->depth == 32
? ZAURA_SURFACE_FRAME_TYPE_NONE
: ZAURA_SURFACE_FRAME_TYPE_SHADOW);
frame_color = window->dark_frame ? ctx->dark_frame_color : ctx->frame_color;
zaura_surface_set_frame_colors(window->aura_surface, frame_color,
frame_color);
zaura_surface_set_startup_id(window->aura_surface, window->startup_id);
sl_update_application_id(ctx, window);
if (ctx->aura_shell->version >=
ZAURA_SURFACE_SET_FULLSCREEN_MODE_SINCE_VERSION) {
zaura_surface_set_fullscreen_mode(window->aura_surface,
ctx->fullscreen_mode);
}
}
// Always use top-level surface for X11 windows as we can't control when the
// window is closed.
if (ctx->xwayland || !parent) {
if (!window->xdg_toplevel) {
window->xdg_toplevel = xdg_surface_get_toplevel(window->xdg_surface);
xdg_toplevel_add_listener(window->xdg_toplevel,
&sl_internal_xdg_toplevel_listener, window);
}
if (parent)
xdg_toplevel_set_parent(window->xdg_toplevel, parent->xdg_toplevel);
if (window->name)
xdg_toplevel_set_title(window->xdg_toplevel, window->name);
if (window->size_flags & P_MIN_SIZE) {
int32_t minw = window->min_width;
int32_t minh = window->min_height;
sl_transform_guest_to_host(window->ctx, window->paired_surface, &minw,
&minh);
xdg_toplevel_set_min_size(window->xdg_toplevel, minw, minh);
}
if (window->size_flags & P_MAX_SIZE) {
int32_t maxw = window->max_width;
int32_t maxh = window->max_height;
sl_transform_guest_to_host(window->ctx, window->paired_surface, &maxw,
&maxh);
xdg_toplevel_set_max_size(window->xdg_toplevel, maxw, maxh);
}
if (window->maximized) {
xdg_toplevel_set_maximized(window->xdg_toplevel);
}
if (window->fullscreen) {
xdg_toplevel_set_fullscreen(window->xdg_toplevel, NULL);
}
} else if (!window->xdg_popup) {
struct xdg_positioner* positioner;
int32_t diffx = window->x - parent->x;
int32_t diffy = window->y - parent->y;
positioner = xdg_wm_base_create_positioner(ctx->xdg_shell->internal);
assert(positioner);
sl_transform_guest_to_host(window->ctx, window->paired_surface, &diffx,
&diffy);
xdg_positioner_set_anchor(positioner, XDG_POSITIONER_ANCHOR_TOP_LEFT);
xdg_positioner_set_gravity(positioner, XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT);
xdg_positioner_set_anchor_rect(positioner, diffx, diffy, 1, 1);
window->xdg_popup = xdg_surface_get_popup(window->xdg_surface,
parent->xdg_surface, positioner);
xdg_popup_add_listener(window->xdg_popup, &sl_internal_xdg_popup_listener,
window);
xdg_positioner_destroy(positioner);
}
if ((window->size_flags & (US_POSITION | P_POSITION)) && parent &&
ctx->aura_shell) {
int32_t diffx = window->x - parent->x;
int32_t diffy = window->y - parent->y;
sl_transform_guest_to_host(window->ctx, window->paired_surface, &diffx,
&diffy);
zaura_surface_set_parent(window->aura_surface, parent->aura_surface, diffx,
diffy);
}
#ifdef COMMIT_LOOP_FIX
sl_commit(window, host_surface);
#else
wl_surface_commit(host_surface->proxy);
#endif
if (host_surface->contents_width && host_surface->contents_height)
window->realized = 1;
}

View File

@ -0,0 +1,198 @@
// Copyright 2021 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef VM_TOOLS_SOMMELIER_SOMMELIER_WINDOW_H_
#define VM_TOOLS_SOMMELIER_SOMMELIER_WINDOW_H_
#include <pixman.h>
#include <wayland-server-core.h>
#include <string>
#include <xcb/xcb.h>
#define US_POSITION (1L << 0)
#define US_SIZE (1L << 1)
#define P_POSITION (1L << 2)
#define P_SIZE (1L << 3)
#define P_MIN_SIZE (1L << 4)
#define P_MAX_SIZE (1L << 5)
#define P_RESIZE_INC (1L << 6)
#define P_ASPECT (1L << 7)
#define P_BASE_SIZE (1L << 8)
#define P_WIN_GRAVITY (1L << 9)
struct sl_config {
uint32_t serial = 0;
uint32_t mask = 0;
uint32_t values[5];
uint32_t states_length = 0;
uint32_t states[3];
};
struct sl_host_surface;
struct sl_window {
sl_window(struct sl_context* ctx,
xcb_window_t id,
int x,
int y,
int width,
int height,
int border_width);
~sl_window();
struct sl_context* ctx = nullptr;
xcb_window_t id = XCB_WINDOW_NONE;
xcb_window_t frame_id = XCB_WINDOW_NONE;
uint32_t host_surface_id = 0;
int unpaired = 1;
bool shaped = false;
int x = 0;
int y = 0;
int width = 0;
int height = 0;
int border_width = 0;
int depth = 0;
int managed = 0;
int realized = 0;
int activated = 0;
int fullscreen = 0;
int compositor_fullscreen = 0;
int maximized = 0;
int iconified = 0;
// True if there has been changes to the fullscreen/maximized state
// while this window is iconified.
bool pending_fullscreen_change = false;
bool pending_maximized_change = false;
int allow_resize = 1;
xcb_window_t transient_for = XCB_WINDOW_NONE;
xcb_window_t client_leader = XCB_WINDOW_NONE;
int decorated = 0;
char* name = nullptr;
bool has_net_wm_name = false;
char* clazz = nullptr;
char* startup_id = nullptr;
std::string app_id_property;
int dark_frame = 0;
uint32_t size_flags = P_POSITION;
int focus_model_take_focus = 0;
int min_width = 0;
int min_height = 0;
int max_width = 0;
int max_height = 0;
struct sl_config next_config;
struct sl_config pending_config;
struct xdg_surface* xdg_surface = nullptr;
struct xdg_toplevel* xdg_toplevel = nullptr;
struct xdg_popup* xdg_popup = nullptr;
struct zaura_surface* aura_surface = nullptr;
struct sl_host_surface* paired_surface = nullptr;
struct pixman_region32 shape_rectangles;
struct wl_list link = {};
};
enum {
PROPERTY_WM_NAME,
PROPERTY_NET_WM_NAME,
PROPERTY_WM_CLASS,
PROPERTY_WM_TRANSIENT_FOR,
PROPERTY_WM_NORMAL_HINTS,
PROPERTY_WM_CLIENT_LEADER,
PROPERTY_WM_PROTOCOLS,
PROPERTY_MOTIF_WM_HINTS,
PROPERTY_NET_STARTUP_ID,
PROPERTY_NET_WM_STATE,
PROPERTY_GTK_THEME_VARIANT,
PROPERTY_XWAYLAND_RANDR_EMU_MONITOR_RECTS,
// The atom corresponding to this property changes depending on the
// --application-id-format command-line argument.
PROPERTY_SPECIFIED_FOR_APP_ID,
};
struct sl_wm_size_hints {
uint32_t flags;
int32_t x, y;
int32_t width, height;
int32_t min_width, min_height;
int32_t max_width, max_height;
int32_t width_inc, height_inc;
struct {
int32_t x;
int32_t y;
} min_aspect, max_aspect;
int32_t base_width, base_height;
int32_t win_gravity;
};
// WM_HINTS is defined at: https://tronche.com/gui/x/icccm/sec-4.html
#define WM_HINTS_FLAG_INPUT (1L << 0)
#define WM_HINTS_FLAG_STATE (1L << 1)
#define WM_HINTS_FLAG_ICON_PIXMAP (1L << 2)
#define WM_HINTS_FLAG_ICON_WINDOW (1L << 3)
#define WM_HINTS_FLAG_ICON_POSITION (1L << 4)
#define WM_HINTS_FLAG_ICON_MASK (1L << 5)
#define WM_HINTS_FLAG_WINDOW_GROUP (1L << 6)
#define WM_HINTS_FLAG_MESSAGE (1L << 7)
#define WM_HINTS_FLAG_URGENCY (1L << 8)
struct sl_wm_hints {
uint32_t flags;
uint32_t input;
uint32_t initial_state;
xcb_pixmap_t icon_pixmap;
xcb_window_t icon_window;
int32_t icon_x;
int32_t icon_y;
xcb_pixmap_t icon_mask;
};
#define MWM_HINTS_FUNCTIONS (1L << 0)
#define MWM_HINTS_DECORATIONS (1L << 1)
#define MWM_HINTS_INPUT_MODE (1L << 2)
#define MWM_HINTS_STATUS (1L << 3)
#define MWM_DECOR_ALL (1L << 0)
#define MWM_DECOR_BORDER (1L << 1)
#define MWM_DECOR_RESIZEH (1L << 2)
#define MWM_DECOR_TITLE (1L << 3)
#define MWM_DECOR_MENU (1L << 4)
#define MWM_DECOR_MINIMIZE (1L << 5)
#define MWM_DECOR_MAXIMIZE (1L << 6)
struct sl_mwm_hints {
uint32_t flags;
uint32_t functions;
uint32_t decorations;
int32_t input_mode;
uint32_t status;
};
#define NET_WM_MOVERESIZE_SIZE_TOPLEFT 0
#define NET_WM_MOVERESIZE_SIZE_TOP 1
#define NET_WM_MOVERESIZE_SIZE_TOPRIGHT 2
#define NET_WM_MOVERESIZE_SIZE_RIGHT 3
#define NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT 4
#define NET_WM_MOVERESIZE_SIZE_BOTTOM 5
#define NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT 6
#define NET_WM_MOVERESIZE_SIZE_LEFT 7
#define NET_WM_MOVERESIZE_MOVE 8
#define NET_WM_STATE_REMOVE 0
#define NET_WM_STATE_ADD 1
#define NET_WM_STATE_TOGGLE 2
#define WM_STATE_WITHDRAWN 0
#define WM_STATE_NORMAL 1
#define WM_STATE_ICONIC 3
void sl_window_update(struct sl_window* window);
void sl_update_application_id(struct sl_context* ctx, struct sl_window* window);
void sl_configure_window(struct sl_window* window);
void sl_send_configure_notify(struct sl_window* window);
int sl_process_pending_configure_acks(struct sl_window* window,
struct sl_host_surface* host_surface);
#endif // VM_TOOLS_SOMMELIER_SOMMELIER_WINDOW_H_

View File

@ -1,563 +0,0 @@
// Copyright 2018 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "sommelier.h"
#include <assert.h>
#include <stdlib.h>
#include "xdg-shell-unstable-v6-client-protocol.h"
#include "xdg-shell-unstable-v6-server-protocol.h"
struct sl_host_xdg_shell {
struct sl_context* ctx;
struct wl_resource* resource;
struct zxdg_shell_v6* proxy;
};
struct sl_host_xdg_surface {
struct sl_context* ctx;
struct wl_resource* resource;
struct zxdg_surface_v6* proxy;
};
struct sl_host_xdg_toplevel {
struct sl_context* ctx;
struct wl_resource* resource;
struct zxdg_toplevel_v6* proxy;
};
struct sl_host_xdg_popup {
struct sl_context* ctx;
struct wl_resource* resource;
struct zxdg_popup_v6* proxy;
};
struct sl_host_xdg_positioner {
struct sl_context* ctx;
struct wl_resource* resource;
struct zxdg_positioner_v6* proxy;
};
static void sl_xdg_positioner_destroy(struct wl_client* client,
struct wl_resource* resource) {
wl_resource_destroy(resource);
}
static void sl_xdg_positioner_set_size(struct wl_client* client,
struct wl_resource* resource,
int32_t width,
int32_t height) {
struct sl_host_xdg_positioner* host = wl_resource_get_user_data(resource);
double scale = host->ctx->scale;
zxdg_positioner_v6_set_size(host->proxy, width / scale, height / scale);
}
static void sl_xdg_positioner_set_anchor_rect(struct wl_client* client,
struct wl_resource* resource,
int32_t x,
int32_t y,
int32_t width,
int32_t height) {
struct sl_host_xdg_positioner* host = wl_resource_get_user_data(resource);
double scale = host->ctx->scale;
int32_t x1, y1, x2, y2;
x1 = x / scale;
y1 = y / scale;
x2 = (x + width) / scale;
y2 = (y + height) / scale;
zxdg_positioner_v6_set_anchor_rect(host->proxy, x1, y1, x2 - x1, y2 - y1);
}
static void sl_xdg_positioner_set_anchor(struct wl_client* client,
struct wl_resource* resource,
uint32_t anchor) {
struct sl_host_xdg_positioner* host = wl_resource_get_user_data(resource);
zxdg_positioner_v6_set_anchor(host->proxy, anchor);
}
static void sl_xdg_positioner_set_gravity(struct wl_client* client,
struct wl_resource* resource,
uint32_t gravity) {
struct sl_host_xdg_positioner* host = wl_resource_get_user_data(resource);
zxdg_positioner_v6_set_gravity(host->proxy, gravity);
}
static void sl_xdg_positioner_set_constraint_adjustment(
struct wl_client* client,
struct wl_resource* resource,
uint32_t constraint_adjustment) {
struct sl_host_xdg_positioner* host = wl_resource_get_user_data(resource);
zxdg_positioner_v6_set_constraint_adjustment(host->proxy,
constraint_adjustment);
}
static void sl_xdg_positioner_set_offset(struct wl_client* client,
struct wl_resource* resource,
int32_t x,
int32_t y) {
struct sl_host_xdg_positioner* host = wl_resource_get_user_data(resource);
double scale = host->ctx->scale;
zxdg_positioner_v6_set_offset(host->proxy, x / scale, y / scale);
}
static const struct zxdg_positioner_v6_interface
sl_xdg_positioner_implementation = {
sl_xdg_positioner_destroy,
sl_xdg_positioner_set_size,
sl_xdg_positioner_set_anchor_rect,
sl_xdg_positioner_set_anchor,
sl_xdg_positioner_set_gravity,
sl_xdg_positioner_set_constraint_adjustment,
sl_xdg_positioner_set_offset};
static void sl_destroy_host_xdg_positioner(struct wl_resource* resource) {
struct sl_host_xdg_positioner* host = wl_resource_get_user_data(resource);
zxdg_positioner_v6_destroy(host->proxy);
wl_resource_set_user_data(resource, NULL);
free(host);
}
static void sl_xdg_popup_destroy(struct wl_client* client,
struct wl_resource* resource) {
wl_resource_destroy(resource);
}
static void sl_xdg_popup_grab(struct wl_client* client,
struct wl_resource* resource,
struct wl_resource* seat_resource,
uint32_t serial) {
struct sl_host_xdg_popup* host = wl_resource_get_user_data(resource);
struct sl_host_seat* host_seat = wl_resource_get_user_data(seat_resource);
zxdg_popup_v6_grab(host->proxy, host_seat->proxy, serial);
}
static const struct zxdg_popup_v6_interface sl_xdg_popup_implementation = {
sl_xdg_popup_destroy, sl_xdg_popup_grab};
static void sl_xdg_popup_configure(void* data,
struct zxdg_popup_v6* xdg_popup,
int32_t x,
int32_t y,
int32_t width,
int32_t height) {
struct sl_host_xdg_popup* host = zxdg_popup_v6_get_user_data(xdg_popup);
double scale = host->ctx->scale;
int32_t x1, y1, x2, y2;
x1 = x * scale;
y1 = y * scale;
x2 = (x + width) * scale;
y2 = (y + height) * scale;
zxdg_popup_v6_send_configure(host->resource, x1, y1, x2 - x1, y2 - y1);
}
static void sl_xdg_popup_popup_done(void* data,
struct zxdg_popup_v6* xdg_popup) {
struct sl_host_xdg_popup* host = zxdg_popup_v6_get_user_data(xdg_popup);
zxdg_popup_v6_send_popup_done(host->resource);
}
static const struct zxdg_popup_v6_listener sl_xdg_popup_listener = {
sl_xdg_popup_configure, sl_xdg_popup_popup_done};
static void sl_destroy_host_xdg_popup(struct wl_resource* resource) {
struct sl_host_xdg_popup* host = wl_resource_get_user_data(resource);
zxdg_popup_v6_destroy(host->proxy);
wl_resource_set_user_data(resource, NULL);
free(host);
}
static void sl_xdg_toplevel_destroy(struct wl_client* client,
struct wl_resource* resource) {
wl_resource_destroy(resource);
}
static void sl_xdg_toplevel_set_parent(struct wl_client* client,
struct wl_resource* resource,
struct wl_resource* parent_resource) {
struct sl_host_xdg_toplevel* host = wl_resource_get_user_data(resource);
struct sl_host_xdg_toplevel* host_parent =
parent_resource ? wl_resource_get_user_data(parent_resource) : NULL;
zxdg_toplevel_v6_set_parent(host->proxy,
host_parent ? host_parent->proxy : NULL);
}
static void sl_xdg_toplevel_set_title(struct wl_client* client,
struct wl_resource* resource,
const char* title) {
struct sl_host_xdg_toplevel* host = wl_resource_get_user_data(resource);
zxdg_toplevel_v6_set_title(host->proxy, title);
}
static void sl_xdg_toplevel_set_app_id(struct wl_client* client,
struct wl_resource* resource,
const char* app_id) {
struct sl_host_xdg_toplevel* host = wl_resource_get_user_data(resource);
zxdg_toplevel_v6_set_app_id(host->proxy, app_id);
}
static void sl_xdg_toplevel_show_window_menu(struct wl_client* client,
struct wl_resource* resource,
struct wl_resource* seat_resource,
uint32_t serial,
int32_t x,
int32_t y) {
struct sl_host_xdg_toplevel* host = wl_resource_get_user_data(resource);
struct sl_host_seat* host_seat =
seat_resource ? wl_resource_get_user_data(seat_resource) : NULL;
zxdg_toplevel_v6_show_window_menu(
host->proxy, host_seat ? host_seat->proxy : NULL, serial, x, y);
}
static void sl_xdg_toplevel_move(struct wl_client* client,
struct wl_resource* resource,
struct wl_resource* seat_resource,
uint32_t serial) {
struct sl_host_xdg_toplevel* host = wl_resource_get_user_data(resource);
struct sl_host_seat* host_seat =
seat_resource ? wl_resource_get_user_data(seat_resource) : NULL;
zxdg_toplevel_v6_move(host->proxy, host_seat ? host_seat->proxy : NULL,
serial);
}
static void sl_xdg_toplevel_resize(struct wl_client* client,
struct wl_resource* resource,
struct wl_resource* seat_resource,
uint32_t serial,
uint32_t edges) {
struct sl_host_xdg_toplevel* host = wl_resource_get_user_data(resource);
struct sl_host_seat* host_seat =
seat_resource ? wl_resource_get_user_data(seat_resource) : NULL;
zxdg_toplevel_v6_resize(host->proxy, host_seat ? host_seat->proxy : NULL,
serial, edges);
}
static void sl_xdg_toplevel_set_max_size(struct wl_client* client,
struct wl_resource* resource,
int32_t width,
int32_t height) {
struct sl_host_xdg_toplevel* host = wl_resource_get_user_data(resource);
zxdg_toplevel_v6_set_max_size(host->proxy, width, height);
}
static void sl_xdg_toplevel_set_min_size(struct wl_client* client,
struct wl_resource* resource,
int32_t width,
int32_t height) {
struct sl_host_xdg_toplevel* host = wl_resource_get_user_data(resource);
zxdg_toplevel_v6_set_min_size(host->proxy, width, height);
}
static void sl_xdg_toplevel_set_maximized(struct wl_client* client,
struct wl_resource* resource) {
struct sl_host_xdg_toplevel* host = wl_resource_get_user_data(resource);
zxdg_toplevel_v6_set_maximized(host->proxy);
}
static void sl_xdg_toplevel_unset_maximized(struct wl_client* client,
struct wl_resource* resource) {
struct sl_host_xdg_toplevel* host = wl_resource_get_user_data(resource);
zxdg_toplevel_v6_unset_maximized(host->proxy);
}
static void sl_xdg_toplevel_set_fullscreen(
struct wl_client* client,
struct wl_resource* resource,
struct wl_resource* output_resource) {
struct sl_host_xdg_toplevel* host = wl_resource_get_user_data(resource);
struct sl_host_output* host_output =
output_resource ? wl_resource_get_user_data(output_resource) : NULL;
zxdg_toplevel_v6_set_fullscreen(host->proxy,
host_output ? host_output->proxy : NULL);
}
static void sl_xdg_toplevel_unset_fullscreen(struct wl_client* client,
struct wl_resource* resource) {
struct sl_host_xdg_toplevel* host = wl_resource_get_user_data(resource);
zxdg_toplevel_v6_unset_fullscreen(host->proxy);
}
static void sl_xdg_toplevel_set_minimized(struct wl_client* client,
struct wl_resource* resource) {
struct sl_host_xdg_toplevel* host = wl_resource_get_user_data(resource);
zxdg_toplevel_v6_set_minimized(host->proxy);
}
static const struct zxdg_toplevel_v6_interface sl_xdg_toplevel_implementation =
{sl_xdg_toplevel_destroy, sl_xdg_toplevel_set_parent,
sl_xdg_toplevel_set_title, sl_xdg_toplevel_set_app_id,
sl_xdg_toplevel_show_window_menu, sl_xdg_toplevel_move,
sl_xdg_toplevel_resize, sl_xdg_toplevel_set_max_size,
sl_xdg_toplevel_set_min_size, sl_xdg_toplevel_set_maximized,
sl_xdg_toplevel_unset_maximized, sl_xdg_toplevel_set_fullscreen,
sl_xdg_toplevel_unset_fullscreen, sl_xdg_toplevel_set_minimized};
static void sl_xdg_toplevel_configure(void* data,
struct zxdg_toplevel_v6* xdg_toplevel,
int32_t width,
int32_t height,
struct wl_array* states) {
struct sl_host_xdg_toplevel* host =
zxdg_toplevel_v6_get_user_data(xdg_toplevel);
double scale = host->ctx->scale;
zxdg_toplevel_v6_send_configure(host->resource, width * scale, height * scale,
states);
}
static void sl_xdg_toplevel_close(void* data,
struct zxdg_toplevel_v6* xdg_toplevel) {
struct sl_host_xdg_toplevel* host =
zxdg_toplevel_v6_get_user_data(xdg_toplevel);
zxdg_toplevel_v6_send_close(host->resource);
}
static const struct zxdg_toplevel_v6_listener sl_xdg_toplevel_listener = {
sl_xdg_toplevel_configure, sl_xdg_toplevel_close};
static void sl_destroy_host_xdg_toplevel(struct wl_resource* resource) {
struct sl_host_xdg_toplevel* host = wl_resource_get_user_data(resource);
zxdg_toplevel_v6_destroy(host->proxy);
wl_resource_set_user_data(resource, NULL);
free(host);
}
static void sl_xdg_surface_destroy(struct wl_client* client,
struct wl_resource* resource) {
wl_resource_destroy(resource);
}
static void sl_xdg_surface_get_toplevel(struct wl_client* client,
struct wl_resource* resource,
uint32_t id) {
struct sl_host_xdg_surface* host = wl_resource_get_user_data(resource);
struct sl_host_xdg_toplevel* host_xdg_toplevel;
host_xdg_toplevel = malloc(sizeof(*host_xdg_toplevel));
assert(host_xdg_toplevel);
host_xdg_toplevel->ctx = host->ctx;
host_xdg_toplevel->resource =
wl_resource_create(client, &zxdg_toplevel_v6_interface, 1, id);
wl_resource_set_implementation(
host_xdg_toplevel->resource, &sl_xdg_toplevel_implementation,
host_xdg_toplevel, sl_destroy_host_xdg_toplevel);
host_xdg_toplevel->proxy = zxdg_surface_v6_get_toplevel(host->proxy);
zxdg_toplevel_v6_set_user_data(host_xdg_toplevel->proxy, host_xdg_toplevel);
zxdg_toplevel_v6_add_listener(host_xdg_toplevel->proxy,
&sl_xdg_toplevel_listener, host_xdg_toplevel);
}
static void sl_xdg_surface_get_popup(struct wl_client* client,
struct wl_resource* resource,
uint32_t id,
struct wl_resource* parent_resource,
struct wl_resource* positioner_resource) {
struct sl_host_xdg_surface* host = wl_resource_get_user_data(resource);
struct sl_host_xdg_surface* host_parent =
wl_resource_get_user_data(parent_resource);
struct sl_host_xdg_positioner* host_positioner =
wl_resource_get_user_data(positioner_resource);
struct sl_host_xdg_popup* host_xdg_popup;
host_xdg_popup = malloc(sizeof(*host_xdg_popup));
assert(host_xdg_popup);
host_xdg_popup->ctx = host->ctx;
host_xdg_popup->resource =
wl_resource_create(client, &zxdg_popup_v6_interface, 1, id);
wl_resource_set_implementation(host_xdg_popup->resource,
&sl_xdg_popup_implementation, host_xdg_popup,
sl_destroy_host_xdg_popup);
host_xdg_popup->proxy = zxdg_surface_v6_get_popup(
host->proxy, host_parent->proxy, host_positioner->proxy);
zxdg_popup_v6_set_user_data(host_xdg_popup->proxy, host_xdg_popup);
zxdg_popup_v6_add_listener(host_xdg_popup->proxy, &sl_xdg_popup_listener,
host_xdg_popup);
}
static void sl_xdg_surface_set_window_geometry(struct wl_client* client,
struct wl_resource* resource,
int32_t x,
int32_t y,
int32_t width,
int32_t height) {
struct sl_host_xdg_surface* host = wl_resource_get_user_data(resource);
double scale = host->ctx->scale;
int32_t x1, y1, x2, y2;
x1 = x / scale;
y1 = y / scale;
x2 = (x + width) / scale;
y2 = (y + height) / scale;
zxdg_surface_v6_set_window_geometry(host->proxy, x1, y1, x2 - x1, y2 - y1);
}
static void sl_xdg_surface_ack_configure(struct wl_client* client,
struct wl_resource* resource,
uint32_t serial) {
struct sl_host_xdg_surface* host = wl_resource_get_user_data(resource);
zxdg_surface_v6_ack_configure(host->proxy, serial);
}
static const struct zxdg_surface_v6_interface sl_xdg_surface_implementation = {
sl_xdg_surface_destroy, sl_xdg_surface_get_toplevel,
sl_xdg_surface_get_popup, sl_xdg_surface_set_window_geometry,
sl_xdg_surface_ack_configure};
static void sl_xdg_surface_configure(void* data,
struct zxdg_surface_v6* xdg_surface,
uint32_t serial) {
struct sl_host_xdg_surface* host = zxdg_surface_v6_get_user_data(xdg_surface);
zxdg_surface_v6_send_configure(host->resource, serial);
}
static const struct zxdg_surface_v6_listener sl_xdg_surface_listener = {
sl_xdg_surface_configure};
static void sl_destroy_host_xdg_surface(struct wl_resource* resource) {
struct sl_host_xdg_surface* host = wl_resource_get_user_data(resource);
zxdg_surface_v6_destroy(host->proxy);
wl_resource_set_user_data(resource, NULL);
free(host);
}
static void sl_xdg_shell_destroy(struct wl_client* client,
struct wl_resource* resource) {
wl_resource_destroy(resource);
}
static void sl_xdg_shell_create_positioner(struct wl_client* client,
struct wl_resource* resource,
uint32_t id) {
struct sl_host_xdg_shell* host = wl_resource_get_user_data(resource);
struct sl_host_xdg_positioner* host_xdg_positioner;
host_xdg_positioner = malloc(sizeof(*host_xdg_positioner));
assert(host_xdg_positioner);
host_xdg_positioner->ctx = host->ctx;
host_xdg_positioner->resource =
wl_resource_create(client, &zxdg_positioner_v6_interface, 1, id);
wl_resource_set_implementation(
host_xdg_positioner->resource, &sl_xdg_positioner_implementation,
host_xdg_positioner, sl_destroy_host_xdg_positioner);
host_xdg_positioner->proxy = zxdg_shell_v6_create_positioner(host->proxy);
zxdg_positioner_v6_set_user_data(host_xdg_positioner->proxy,
host_xdg_positioner);
}
static void sl_xdg_shell_get_xdg_surface(struct wl_client* client,
struct wl_resource* resource,
uint32_t id,
struct wl_resource* surface_resource) {
struct sl_host_xdg_shell* host = wl_resource_get_user_data(resource);
struct sl_host_surface* host_surface =
wl_resource_get_user_data(surface_resource);
struct sl_host_xdg_surface* host_xdg_surface;
host_xdg_surface = malloc(sizeof(*host_xdg_surface));
assert(host_xdg_surface);
host_xdg_surface->ctx = host->ctx;
host_xdg_surface->resource =
wl_resource_create(client, &zxdg_surface_v6_interface, 1, id);
wl_resource_set_implementation(host_xdg_surface->resource,
&sl_xdg_surface_implementation,
host_xdg_surface, sl_destroy_host_xdg_surface);
host_xdg_surface->proxy =
zxdg_shell_v6_get_xdg_surface(host->proxy, host_surface->proxy);
zxdg_surface_v6_set_user_data(host_xdg_surface->proxy, host_xdg_surface);
zxdg_surface_v6_add_listener(host_xdg_surface->proxy,
&sl_xdg_surface_listener, host_xdg_surface);
host_surface->has_role = 1;
}
static void sl_xdg_shell_pong(struct wl_client* client,
struct wl_resource* resource,
uint32_t serial) {
struct sl_host_xdg_shell* host = wl_resource_get_user_data(resource);
zxdg_shell_v6_pong(host->proxy, serial);
}
static const struct zxdg_shell_v6_interface sl_xdg_shell_implementation = {
sl_xdg_shell_destroy, sl_xdg_shell_create_positioner,
sl_xdg_shell_get_xdg_surface, sl_xdg_shell_pong};
static void sl_xdg_shell_ping(void* data,
struct zxdg_shell_v6* xdg_shell,
uint32_t serial) {
struct sl_host_xdg_shell* host = zxdg_shell_v6_get_user_data(xdg_shell);
zxdg_shell_v6_send_ping(host->resource, serial);
}
static const struct zxdg_shell_v6_listener sl_xdg_shell_listener = {
sl_xdg_shell_ping};
static void sl_destroy_host_xdg_shell(struct wl_resource* resource) {
struct sl_host_xdg_shell* host = wl_resource_get_user_data(resource);
zxdg_shell_v6_destroy(host->proxy);
wl_resource_set_user_data(resource, NULL);
free(host);
}
static void sl_bind_host_xdg_shell(struct wl_client* client,
void* data,
uint32_t version,
uint32_t id) {
struct sl_context* ctx = (struct sl_context*)data;
struct sl_host_xdg_shell* host;
host = malloc(sizeof(*host));
assert(host);
host->ctx = ctx;
host->resource = wl_resource_create(client, &zxdg_shell_v6_interface, 1, id);
wl_resource_set_implementation(host->resource, &sl_xdg_shell_implementation,
host, sl_destroy_host_xdg_shell);
host->proxy =
wl_registry_bind(wl_display_get_registry(ctx->display),
ctx->xdg_shell->id, &zxdg_shell_v6_interface, 1);
zxdg_shell_v6_set_user_data(host->proxy, host);
zxdg_shell_v6_add_listener(host->proxy, &sl_xdg_shell_listener, host);
}
struct sl_global* sl_xdg_shell_global_create(struct sl_context* ctx) {
return sl_global_create(ctx, &zxdg_shell_v6_interface, 1, ctx,
sl_bind_host_xdg_shell);
}

View File

@ -0,0 +1,450 @@
// Copyright 2018 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "sommelier.h" // NOLINT(build/include_directory)
#include "sommelier-transform.h" // NOLINT(build/include_directory)
#include <assert.h>
#include <stdlib.h>
#include "xdg-shell-client-protocol.h" // NOLINT(build/include_directory)
#include "xdg-shell-server-protocol.h" // NOLINT(build/include_directory)
struct sl_host_xdg_shell {
struct sl_context* ctx;
struct wl_resource* resource;
struct xdg_wm_base* proxy;
};
MAP_STRUCTS(xdg_wm_base, sl_host_xdg_shell);
struct sl_host_xdg_surface {
struct sl_context* ctx;
struct wl_resource* resource;
struct xdg_surface* proxy;
struct sl_host_surface* originator;
};
MAP_STRUCTS(xdg_surface, sl_host_xdg_surface);
struct sl_host_xdg_toplevel {
struct sl_context* ctx;
struct wl_resource* resource;
struct xdg_toplevel* proxy;
struct sl_host_xdg_surface* originator;
};
MAP_STRUCTS(xdg_toplevel, sl_host_xdg_toplevel);
struct sl_host_xdg_popup {
struct sl_context* ctx;
struct wl_resource* resource;
struct xdg_popup* proxy;
struct sl_host_xdg_surface* originator;
};
MAP_STRUCTS(xdg_popup, sl_host_xdg_popup);
struct sl_host_xdg_positioner {
struct sl_context* ctx;
struct wl_resource* resource;
struct xdg_positioner* proxy;
};
MAP_STRUCTS(xdg_positioner, sl_host_xdg_positioner);
static void sl_xdg_positioner_destroy(struct wl_client* client,
struct wl_resource* resource) {
wl_resource_destroy(resource);
}
static void sl_xdg_positioner_set_size(struct wl_client* client,
struct wl_resource* resource,
int32_t width,
int32_t height) {
struct sl_host_xdg_positioner* host =
static_cast<sl_host_xdg_positioner*>(wl_resource_get_user_data(resource));
int32_t iwidth = width;
int32_t iheight = height;
sl_transform_guest_to_host(host->ctx, nullptr, &iwidth, &iheight);
xdg_positioner_set_size(host->proxy, iwidth, iheight);
}
static void sl_xdg_positioner_set_anchor_rect(struct wl_client* client,
struct wl_resource* resource,
int32_t x,
int32_t y,
int32_t width,
int32_t height) {
struct sl_host_xdg_positioner* host =
static_cast<sl_host_xdg_positioner*>(wl_resource_get_user_data(resource));
int32_t x1 = x;
int32_t y1 = y;
int32_t x2 = x + width;
int32_t y2 = y + height;
sl_transform_guest_to_host(host->ctx, nullptr, &x1, &y1);
sl_transform_guest_to_host(host->ctx, nullptr, &x2, &y2);
xdg_positioner_set_anchor_rect(host->proxy, x1, y1, x2 - x1, y2 - y1);
}
static void sl_xdg_positioner_set_offset(struct wl_client* client,
struct wl_resource* resource,
int32_t x,
int32_t y) {
struct sl_host_xdg_positioner* host =
static_cast<sl_host_xdg_positioner*>(wl_resource_get_user_data(resource));
int32_t ix = x, iy = y;
sl_transform_guest_to_host(host->ctx, nullptr, &ix, &iy);
xdg_positioner_set_offset(host->proxy, ix, iy);
}
static const struct xdg_positioner_interface sl_xdg_positioner_implementation =
{sl_xdg_positioner_destroy,
sl_xdg_positioner_set_size,
sl_xdg_positioner_set_anchor_rect,
ForwardRequest<xdg_positioner_set_anchor>,
ForwardRequest<xdg_positioner_set_gravity>,
ForwardRequest<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 =
static_cast<sl_host_xdg_positioner*>(wl_resource_get_user_data(resource));
xdg_positioner_destroy(host->proxy);
wl_resource_set_user_data(resource, NULL);
delete host;
}
static void sl_xdg_popup_destroy(struct wl_client* client,
struct wl_resource* resource) {
wl_resource_destroy(resource);
}
static const struct xdg_popup_interface sl_xdg_popup_implementation = {
sl_xdg_popup_destroy, ForwardRequest<xdg_popup_grab>};
static struct sl_host_surface* get_host_surface(
struct sl_host_xdg_surface* xdg) {
// For xdg_popup/xdg_toplevel they will point to the
// originating xdg_surface. The originating surface
// will point to the source sl_host_surface
if (xdg && xdg->originator)
return xdg->originator;
else
return nullptr;
}
static void sl_xdg_popup_configure(void* data,
struct xdg_popup* xdg_popup,
int32_t x,
int32_t y,
int32_t width,
int32_t height) {
struct sl_host_xdg_popup* host =
static_cast<sl_host_xdg_popup*>(xdg_popup_get_user_data(xdg_popup));
int32_t x1 = x;
int32_t y1 = y;
int32_t x2 = x + width;
int32_t y2 = y + height;
sl_transform_host_to_guest(host->ctx, get_host_surface(host->originator), &x1,
&y1);
sl_transform_host_to_guest(host->ctx, get_host_surface(host->originator), &x2,
&y2);
xdg_popup_send_configure(host->resource, x1, y1, x2 - x1, y2 - y1);
}
static void sl_xdg_popup_popup_done(void* data, struct xdg_popup* xdg_popup) {
struct sl_host_xdg_popup* host =
static_cast<sl_host_xdg_popup*>(xdg_popup_get_user_data(xdg_popup));
xdg_popup_send_popup_done(host->resource);
}
static const struct xdg_popup_listener sl_xdg_popup_listener = {
sl_xdg_popup_configure, sl_xdg_popup_popup_done};
static void sl_destroy_host_xdg_popup(struct wl_resource* resource) {
struct sl_host_xdg_popup* host =
static_cast<sl_host_xdg_popup*>(wl_resource_get_user_data(resource));
xdg_popup_destroy(host->proxy);
wl_resource_set_user_data(resource, NULL);
delete host;
}
static void sl_xdg_toplevel_destroy(struct wl_client* client,
struct wl_resource* resource) {
wl_resource_destroy(resource);
}
static void sl_xdg_toplevel_show_window_menu(struct wl_client* client,
struct wl_resource* resource,
struct wl_resource* seat_resource,
uint32_t serial,
int32_t x,
int32_t y) {
struct sl_host_xdg_toplevel* host =
static_cast<sl_host_xdg_toplevel*>(wl_resource_get_user_data(resource));
struct sl_host_seat* host_seat =
seat_resource
? static_cast<sl_host_seat*>(wl_resource_get_user_data(seat_resource))
: NULL;
// TODO(mrisaacb): There was no scaling performed here in the original code.
// Figure out why this was.
xdg_toplevel_show_window_menu(
host->proxy, host_seat ? host_seat->proxy : NULL, serial, x, y);
} // NOLINT(whitespace/indent)
static const struct xdg_toplevel_interface sl_xdg_toplevel_implementation = {
sl_xdg_toplevel_destroy,
ForwardRequest<xdg_toplevel_set_parent, AllowNullResource::kYes>,
ForwardRequest<xdg_toplevel_set_title>,
ForwardRequest<xdg_toplevel_set_app_id>,
sl_xdg_toplevel_show_window_menu,
ForwardRequest<xdg_toplevel_move, AllowNullResource::kYes>,
ForwardRequest<xdg_toplevel_resize, AllowNullResource::kYes>,
ForwardRequest<xdg_toplevel_set_max_size>,
ForwardRequest<xdg_toplevel_set_min_size>,
ForwardRequest<xdg_toplevel_set_maximized>,
ForwardRequest<xdg_toplevel_unset_maximized>,
ForwardRequest<xdg_toplevel_set_fullscreen, AllowNullResource::kYes>,
ForwardRequest<xdg_toplevel_unset_fullscreen>,
ForwardRequest<xdg_toplevel_set_minimized>,
};
static void sl_xdg_toplevel_configure(void* data,
struct xdg_toplevel* xdg_toplevel,
int32_t width,
int32_t height,
struct wl_array* states) {
struct sl_host_xdg_toplevel* host = static_cast<sl_host_xdg_toplevel*>(
xdg_toplevel_get_user_data(xdg_toplevel));
int32_t iwidth = width;
int32_t iheight = height;
sl_transform_host_to_guest(host->ctx, get_host_surface(host->originator),
&iwidth, &iheight);
xdg_toplevel_send_configure(host->resource, iwidth, iheight, states);
}
static void sl_xdg_toplevel_close(void* data,
struct xdg_toplevel* xdg_toplevel) {
struct sl_host_xdg_toplevel* host = static_cast<sl_host_xdg_toplevel*>(
xdg_toplevel_get_user_data(xdg_toplevel));
xdg_toplevel_send_close(host->resource);
}
static const struct xdg_toplevel_listener sl_xdg_toplevel_listener = {
sl_xdg_toplevel_configure, sl_xdg_toplevel_close};
static void sl_destroy_host_xdg_toplevel(struct wl_resource* resource) {
struct sl_host_xdg_toplevel* host =
static_cast<sl_host_xdg_toplevel*>(wl_resource_get_user_data(resource));
xdg_toplevel_destroy(host->proxy);
wl_resource_set_user_data(resource, NULL);
delete host;
}
static void sl_xdg_surface_destroy(struct wl_client* client,
struct wl_resource* resource) {
wl_resource_destroy(resource);
}
static void sl_xdg_surface_get_toplevel(struct wl_client* client,
struct wl_resource* resource,
uint32_t id) {
struct sl_host_xdg_surface* host =
static_cast<sl_host_xdg_surface*>(wl_resource_get_user_data(resource));
struct sl_host_xdg_toplevel* host_xdg_toplevel = new sl_host_xdg_toplevel();
host_xdg_toplevel->ctx = host->ctx;
host_xdg_toplevel->resource =
wl_resource_create(client, &xdg_toplevel_interface, 1, id);
wl_resource_set_implementation(
host_xdg_toplevel->resource, &sl_xdg_toplevel_implementation,
host_xdg_toplevel, sl_destroy_host_xdg_toplevel);
host_xdg_toplevel->proxy = xdg_surface_get_toplevel(host->proxy);
host_xdg_toplevel->originator = host;
xdg_toplevel_add_listener(host_xdg_toplevel->proxy, &sl_xdg_toplevel_listener,
host_xdg_toplevel);
}
static void sl_xdg_surface_get_popup(struct wl_client* client,
struct wl_resource* resource,
uint32_t id,
struct wl_resource* parent_resource,
struct wl_resource* positioner_resource) {
struct sl_host_xdg_surface* host =
static_cast<sl_host_xdg_surface*>(wl_resource_get_user_data(resource));
struct sl_host_xdg_surface* host_parent =
parent_resource ? static_cast<sl_host_xdg_surface*>(
wl_resource_get_user_data(parent_resource))
: NULL;
struct sl_host_xdg_positioner* host_positioner =
static_cast<sl_host_xdg_positioner*>(
wl_resource_get_user_data(positioner_resource));
struct sl_host_xdg_popup* host_xdg_popup = new sl_host_xdg_popup();
host_xdg_popup->ctx = host->ctx;
host_xdg_popup->resource =
wl_resource_create(client, &xdg_popup_interface, 1, id);
wl_resource_set_implementation(host_xdg_popup->resource,
&sl_xdg_popup_implementation, host_xdg_popup,
sl_destroy_host_xdg_popup);
host_xdg_popup->proxy = xdg_surface_get_popup(
host->proxy, host_parent ? host_parent->proxy : NULL,
host_positioner->proxy);
host_xdg_popup->originator = host_parent;
xdg_popup_add_listener(host_xdg_popup->proxy, &sl_xdg_popup_listener,
host_xdg_popup);
} // NOLINT(whitespace/indent)
static void sl_xdg_surface_set_window_geometry(struct wl_client* client,
struct wl_resource* resource,
int32_t x,
int32_t y,
int32_t width,
int32_t height) {
struct sl_host_xdg_surface* host =
static_cast<sl_host_xdg_surface*>(wl_resource_get_user_data(resource));
int32_t x1 = x;
int32_t y1 = y;
int32_t x2 = x + width;
int32_t y2 = y + height;
sl_transform_guest_to_host(host->ctx, host->originator, &x1, &y1);
sl_transform_guest_to_host(host->ctx, host->originator, &x2, &y2);
xdg_surface_set_window_geometry(host->proxy, x1, y1, x2 - x1, y2 - y1);
}
static const struct xdg_surface_interface sl_xdg_surface_implementation = {
sl_xdg_surface_destroy, sl_xdg_surface_get_toplevel,
sl_xdg_surface_get_popup, sl_xdg_surface_set_window_geometry,
ForwardRequest<xdg_surface_ack_configure>};
static void sl_xdg_surface_configure(void* data,
struct xdg_surface* xdg_surface,
uint32_t serial) {
struct sl_host_xdg_surface* host =
static_cast<sl_host_xdg_surface*>(xdg_surface_get_user_data(xdg_surface));
xdg_surface_send_configure(host->resource, serial);
}
static const struct xdg_surface_listener sl_xdg_surface_listener = {
sl_xdg_surface_configure};
static void sl_destroy_host_xdg_surface(struct wl_resource* resource) {
struct sl_host_xdg_surface* host =
static_cast<sl_host_xdg_surface*>(wl_resource_get_user_data(resource));
xdg_surface_destroy(host->proxy);
wl_resource_set_user_data(resource, NULL);
delete host;
}
static void sl_xdg_shell_destroy(struct wl_client* client,
struct wl_resource* resource) {
wl_resource_destroy(resource);
}
static void sl_xdg_shell_create_positioner(struct wl_client* client,
struct wl_resource* resource,
uint32_t id) {
struct sl_host_xdg_shell* host =
static_cast<sl_host_xdg_shell*>(wl_resource_get_user_data(resource));
struct sl_host_xdg_positioner* host_xdg_positioner =
new sl_host_xdg_positioner();
host_xdg_positioner->ctx = host->ctx;
host_xdg_positioner->resource =
wl_resource_create(client, &xdg_positioner_interface, 1, id);
wl_resource_set_implementation(
host_xdg_positioner->resource, &sl_xdg_positioner_implementation,
host_xdg_positioner, sl_destroy_host_xdg_positioner);
host_xdg_positioner->proxy = xdg_wm_base_create_positioner(host->proxy);
xdg_positioner_set_user_data(host_xdg_positioner->proxy, host_xdg_positioner);
}
static void sl_xdg_shell_get_xdg_surface(struct wl_client* client,
struct wl_resource* resource,
uint32_t id,
struct wl_resource* surface_resource) {
struct sl_host_xdg_shell* host =
static_cast<sl_host_xdg_shell*>(wl_resource_get_user_data(resource));
struct sl_host_surface* host_surface = static_cast<sl_host_surface*>(
wl_resource_get_user_data(surface_resource));
struct sl_host_xdg_surface* host_xdg_surface = new sl_host_xdg_surface();
host_xdg_surface->ctx = host->ctx;
host_xdg_surface->resource =
wl_resource_create(client, &xdg_surface_interface, 1, id);
wl_resource_set_implementation(host_xdg_surface->resource,
&sl_xdg_surface_implementation,
host_xdg_surface, sl_destroy_host_xdg_surface);
host_xdg_surface->proxy =
xdg_wm_base_get_xdg_surface(host->proxy, host_surface->proxy);
host_xdg_surface->originator = host_surface;
xdg_surface_add_listener(host_xdg_surface->proxy, &sl_xdg_surface_listener,
host_xdg_surface);
host_surface->has_role = 1;
}
static const struct xdg_wm_base_interface sl_xdg_shell_implementation = {
sl_xdg_shell_destroy, sl_xdg_shell_create_positioner,
sl_xdg_shell_get_xdg_surface, ForwardRequest<xdg_wm_base_pong>};
static void sl_xdg_shell_ping(void* data,
struct xdg_wm_base* xdg_shell,
uint32_t serial) {
struct sl_host_xdg_shell* host =
static_cast<sl_host_xdg_shell*>(xdg_wm_base_get_user_data(xdg_shell));
xdg_wm_base_send_ping(host->resource, serial);
}
static const struct xdg_wm_base_listener sl_xdg_shell_listener = {
sl_xdg_shell_ping};
static void sl_destroy_host_xdg_shell(struct wl_resource* resource) {
struct sl_host_xdg_shell* host =
static_cast<sl_host_xdg_shell*>(wl_resource_get_user_data(resource));
xdg_wm_base_destroy(host->proxy);
wl_resource_set_user_data(resource, NULL);
delete host;
}
static void sl_bind_host_xdg_shell(struct wl_client* client,
void* data,
uint32_t version,
uint32_t id) {
struct sl_context* ctx = (struct sl_context*)data;
struct sl_host_xdg_shell* host = new sl_host_xdg_shell();
host->ctx = ctx;
host->resource = wl_resource_create(client, &xdg_wm_base_interface, 1, id);
wl_resource_set_implementation(host->resource, &sl_xdg_shell_implementation,
host, sl_destroy_host_xdg_shell);
host->proxy = static_cast<xdg_wm_base*>(
wl_registry_bind(wl_display_get_registry(ctx->display),
ctx->xdg_shell->id, &xdg_wm_base_interface, 1));
xdg_wm_base_add_listener(host->proxy, &sl_xdg_shell_listener, host);
}
struct sl_global* sl_xdg_shell_global_create(struct sl_context* ctx) {
return sl_global_create(ctx, &xdg_wm_base_interface, 1, ctx,
sl_bind_host_xdg_shell);
}

View File

@ -0,0 +1,192 @@
// Copyright 2022 The ChromiumOS Authors.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <assert.h>
#include <pixman.h>
#include "sommelier.h" // NOLINT(build/include_directory)
#include "sommelier-tracing.h" // NOLINT(build/include_directory)
#include "sommelier-xshape.h" // NOLINT(build/include_directory)
static void sl_clear_shape_region(sl_window* window) {
window->shaped = false;
pixman_region32_fini(&window->shape_rectangles);
}
static void sl_attach_shape_region(struct sl_context* ctx,
xcb_window_t window) {
sl_window* sl_window = nullptr;
xcb_shape_get_rectangles_reply_t* reply;
int i;
sl_window = sl_lookup_window(ctx, window);
if (!sl_window)
return;
reply = xcb_shape_get_rectangles_reply(
ctx->connection,
xcb_shape_get_rectangles(ctx->connection, window, XCB_SHAPE_SK_BOUNDING),
NULL);
if (!reply)
return;
int nrects = xcb_shape_get_rectangles_rectangles_length(reply);
xcb_rectangle_t* rects = xcb_shape_get_rectangles_rectangles(reply);
if (!rects || nrects <= 0)
return;
pixman_box32_t* boxes =
static_cast<pixman_box32_t*>(calloc(sizeof(pixman_box32_t), nrects));
if (!boxes) {
free(reply);
return;
}
for (i = 0; i < nrects; i++) {
boxes[i].x1 = rects[i].x;
boxes[i].y1 = rects[i].y;
boxes[i].x2 = rects[i].x + rects[i].width;
boxes[i].y2 = rects[i].y + rects[i].height;
}
pixman_region32_init_rects(&sl_window->shape_rectangles, boxes, nrects);
free(boxes);
free(reply);
sl_window->shaped = true;
}
void sl_handle_shape_notify(struct sl_context* ctx,
struct xcb_shape_notify_event_t* event) {
sl_window* window = nullptr;
window = sl_lookup_window(ctx, event->affected_window);
if (!window)
return;
sl_clear_shape_region(window);
if (event->shaped)
sl_attach_shape_region(ctx, event->affected_window);
return;
}
void sl_shape_query(struct sl_context* ctx, xcb_window_t xwindow) {
xcb_shape_query_extents_reply_t* reply;
sl_window* sl_window = nullptr;
sl_window = sl_lookup_window(ctx, xwindow);
if (!sl_window)
return;
reply = xcb_shape_query_extents_reply(
ctx->connection, xcb_shape_query_extents(ctx->connection, xwindow), NULL);
if (!reply)
return;
sl_clear_shape_region(sl_window);
if (reply->bounding_shaped) {
sl_attach_shape_region(ctx, xwindow);
}
}
pixman_format_code_t sl_pixman_format_for_shm_format(uint32_t shm_format) {
pixman_format_code_t fmt = PIXMAN_a1;
switch (shm_format) {
case WL_SHM_FORMAT_ARGB8888:
fmt = PIXMAN_a8r8g8b8;
break;
case WL_SHM_FORMAT_XRGB8888:
fmt = PIXMAN_x8r8g8b8;
break;
case WL_SHM_FORMAT_ABGR8888:
fmt = PIXMAN_a8b8g8r8;
break;
case WL_SHM_FORMAT_XBGR8888:
fmt = PIXMAN_x8b8g8r8;
break;
case WL_SHM_FORMAT_RGB565:
fmt = PIXMAN_r5g6b5;
break;
default:
assert(0);
break;
}
return fmt;
}
void sl_xshape_generate_argb_image(struct sl_context* ctx,
pixman_region32_t* shape,
struct sl_mmap* src_mmap,
pixman_image_t* dst_image,
uint32_t src_shm_format) {
int buf_width, buf_height, nrects;
pixman_region32_t intersect_rects;
pixman_image_t* src;
assert(ctx);
assert(shape);
assert(src_mmap);
assert(dst_image);
buf_width = pixman_image_get_width(dst_image);
buf_height = pixman_image_get_height(dst_image);
if (buf_width <= 0 || buf_height <= 0)
return;
// Intersect with the pixmap bounds to ensure we do not perform
// any OOB accesses
// In addition, we can assume the dimensions of the dst_image is
// the same size as the input image
pixman_region32_init(&intersect_rects);
pixman_region32_intersect_rect(&intersect_rects, shape, 0, 0, buf_width,
buf_height);
// With the blank destination image, we will take the source image and the
// shape rectangles and generate the "stamped out" ARGB image.
//
// This is accomplished by clearing out the destination image to be
// completely transparent as a first step. Then for each rectangular
// region within the shape data, we will use pixman_image_composite to
// copy that portion of the image from the source to the ARGB stamp out
// buffer.
//
// pixman_image_composite is used as it will automatically perform pixel
// format conversion for us.
src = pixman_image_create_bits_no_clear(
sl_pixman_format_for_shm_format(src_shm_format), buf_width, buf_height,
reinterpret_cast<uint32_t*>(src_mmap->addr), src_mmap->stride[0]);
pixman_box32_t* rects = pixman_region32_rectangles(&intersect_rects, &nrects);
pixman_color_t clear = {.red = 0, .green = 0, .blue = 0, .alpha = 0};
pixman_box32_t dstbox = {.x1 = 0, .y1 = 0, .x2 = buf_width, .y2 = buf_height};
pixman_image_fill_boxes(PIXMAN_OP_SRC, dst_image, &clear, 1, &dstbox);
for (int i = 0; i < nrects; i++) {
pixman_image_composite(PIXMAN_OP_SRC, src, NULL, dst_image, rects[i].x1,
rects[i].y1, 0, 0, rects[i].x1, rects[i].y1,
(rects[i].x2 - rects[i].x1),
(rects[i].y2 - rects[i].y1));
}
}

View File

@ -0,0 +1,23 @@
// Copyright 2022 The ChromiumOS Authors.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef VM_TOOLS_SOMMELIER_SOMMELIER_XSHAPE_H_
#define VM_TOOLS_SOMMELIER_SOMMELIER_XSHAPE_H_
#include <xcb/shape.h>
#include <xcb/xcb.h>
#include "sommelier-ctx.h" // NOLINT(build/include_directory)
void sl_handle_shape_notify(struct sl_context* ctx,
struct xcb_shape_notify_event_t* event);
void sl_shape_query(struct sl_context* ctx, xcb_window_t xwindow);
void sl_xshape_generate_argb_image(struct sl_context* ctx,
pixman_region32_t* shape,
struct sl_mmap* src_mmap,
pixman_image_t* dst_image,
uint32_t src_shm_format);
#endif // VM_TOOLS_SOMMELIER_SOMMELIER_XSHAPE_H_

File diff suppressed because it is too large Load Diff

View File

@ -1,31 +1,34 @@
// Copyright 2018 The Chromium OS Authors. All rights reserved.
// Copyright 2018 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef VM_TOOLS_SOMMELIER_SOMMELIER_H_
#define VM_TOOLS_SOMMELIER_SOMMELIER_H_
#include <limits.h>
#include <linux/types.h>
#include <sys/types.h>
#include <wayland-server.h>
#include <wayland-util.h>
#include <xcb/xcb.h>
#include <xkbcommon/xkbcommon.h>
#include "config.h"
#include "compositor/sommelier-mmap.h" // NOLINT(build/include_directory)
#include "sommelier-ctx.h" // NOLINT(build/include_directory)
#include "sommelier-global.h" // NOLINT(build/include_directory)
#include "sommelier-util.h" // NOLINT(build/include_directory)
#include "sommelier-window.h" // NOLINT(build/include_directory)
#include "weak-resource-ptr.h" // NOLINT(build/include_directory)
#define SOMMELIER_VERSION "0.20"
#define MIN(a, b) (((a) < (b)) ? (a) : (b))
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
#define UNUSED(x) ((void)(x))
#define CONTROL_MASK (1 << 0)
#define ALT_MASK (1 << 1)
#define SHIFT_MASK (1 << 2)
struct sl_global;
struct sl_compositor;
struct sl_shm;
@ -39,148 +42,41 @@ struct sl_aura_shell;
struct sl_viewporter;
struct sl_linux_dmabuf;
struct sl_keyboard_extension;
struct sl_text_input_extension;
struct sl_text_input_manager;
struct sl_relative_pointer_manager;
struct sl_pointer_constraints;
struct sl_window;
struct sl_host_surface;
struct sl_host_output;
struct zaura_shell;
struct zcr_keyboard_extension_v1;
struct zxdg_output_manager_v1;
enum {
ATOM_WM_S0,
ATOM_WM_PROTOCOLS,
ATOM_WM_STATE,
ATOM_WM_CHANGE_STATE,
ATOM_WM_DELETE_WINDOW,
ATOM_WM_TAKE_FOCUS,
ATOM_WM_CLIENT_LEADER,
ATOM_WL_SURFACE_ID,
ATOM_UTF8_STRING,
ATOM_MOTIF_WM_HINTS,
ATOM_NET_FRAME_EXTENTS,
ATOM_NET_STARTUP_ID,
ATOM_NET_SUPPORTING_WM_CHECK,
ATOM_NET_WM_NAME,
ATOM_NET_WM_MOVERESIZE,
ATOM_NET_WM_STATE,
ATOM_NET_WM_STATE_FULLSCREEN,
ATOM_NET_WM_STATE_MAXIMIZED_VERT,
ATOM_NET_WM_STATE_MAXIMIZED_HORZ,
ATOM_CLIPBOARD,
ATOM_CLIPBOARD_MANAGER,
ATOM_TARGETS,
ATOM_TIMESTAMP,
ATOM_TEXT,
ATOM_INCR,
ATOM_WL_SELECTION,
ATOM_GTK_THEME_VARIANT,
ATOM_LAST = ATOM_GTK_THEME_VARIANT,
};
#ifdef GAMEPAD_SUPPORT
struct sl_gamepad;
struct sl_gaming_input_manager;
struct zcr_gaming_input_v2;
#endif
enum {
SHM_DRIVER_NOOP,
SHM_DRIVER_DMABUF,
SHM_DRIVER_VIRTWL,
SHM_DRIVER_VIRTWL_DMABUF,
};
class WaylandChannel;
enum {
DATA_DRIVER_NOOP,
DATA_DRIVER_VIRTWL,
};
extern const struct wl_registry_listener sl_registry_listener;
struct sl_context {
char** runprog;
struct wl_display* display;
struct wl_display* host_display;
struct wl_client* client;
struct sl_compositor* compositor;
struct sl_subcompositor* subcompositor;
struct sl_shm* shm;
struct sl_shell* shell;
struct sl_data_device_manager* data_device_manager;
struct sl_xdg_shell* xdg_shell;
struct sl_aura_shell* aura_shell;
struct sl_viewporter* viewporter;
struct sl_linux_dmabuf* linux_dmabuf;
struct sl_keyboard_extension* keyboard_extension;
struct sl_text_input_manager* text_input_manager;
struct sl_relative_pointer_manager* relative_pointer_manager;
struct sl_pointer_constraints* pointer_constraints;
struct wl_list outputs;
struct wl_list seats;
struct wl_event_source* display_event_source;
struct wl_event_source* display_ready_event_source;
struct wl_event_source* sigchld_event_source;
struct wl_array dpi;
int shm_driver;
int data_driver;
int wm_fd;
int virtwl_fd;
int virtwl_ctx_fd;
int virtwl_socket_fd;
struct wl_event_source* virtwl_ctx_event_source;
struct wl_event_source* virtwl_socket_event_source;
const char* drm_device;
struct gbm_device* gbm;
int xwayland;
pid_t xwayland_pid;
pid_t child_pid;
pid_t peer_pid;
struct xkb_context* xkb_context;
struct wl_list accelerators;
struct wl_list registries;
struct wl_list globals;
struct wl_list host_outputs;
int next_global_id;
xcb_connection_t* connection;
struct wl_event_source* connection_event_source;
const xcb_query_extension_reply_t* xfixes_extension;
xcb_screen_t* screen;
xcb_window_t window;
struct wl_list windows, unpaired_windows;
struct sl_window* host_focus_window;
int needs_set_input_focus;
double desired_scale;
double scale;
const char* application_id;
int exit_with_child;
const char* sd_notify;
int clipboard_manager;
uint32_t frame_color;
uint32_t dark_frame_color;
struct sl_host_seat* default_seat;
xcb_window_t selection_window;
xcb_window_t selection_owner;
int selection_incremental_transfer;
xcb_selection_request_event_t selection_request;
xcb_timestamp_t selection_timestamp;
struct wl_data_device* selection_data_device;
struct sl_data_offer* selection_data_offer;
struct sl_data_source* selection_data_source;
int selection_data_source_send_fd;
struct wl_list selection_data_source_send_pending;
struct wl_event_source* selection_send_event_source;
xcb_get_property_reply_t* selection_property_reply;
int selection_property_offset;
struct wl_event_source* selection_event_source;
xcb_atom_t selection_data_type;
struct wl_array selection_data;
int selection_data_offer_receive_fd;
int selection_data_ack_pending;
union {
const char* name;
xcb_intern_atom_cookie_t cookie;
xcb_atom_t value;
} atoms[ATOM_LAST + 1];
xcb_visualid_t visual_ids[256];
xcb_colormap_t colormaps[256];
};
// Exported for testing.
void sl_registry_handler(void* data,
struct wl_registry* registry,
uint32_t id,
const char* interface,
uint32_t version);
// We require a host compositor supporting at least this wl_compositor version.
constexpr uint32_t kMinHostWlCompositorVersion =
WL_SURFACE_SET_BUFFER_SCALE_SINCE_VERSION;
struct sl_compositor {
struct sl_context* ctx;
uint32_t id;
uint32_t version;
struct sl_global* host_global;
struct wl_compositor* internal;
};
@ -206,15 +102,18 @@ struct sl_host_pointer {
struct wl_resource* resource;
struct wl_pointer* proxy;
struct wl_resource* focus_resource;
struct sl_host_surface* focus_surface;
struct wl_listener focus_resource_listener;
uint32_t focus_serial;
uint32_t time;
wl_fixed_t axis_delta[2];
int32_t axis_discrete[2];
};
struct sl_relative_pointer_manager {
struct sl_context* ctx;
uint32_t id;
struct sl_global* host_global;
struct zwp_relative_pointer_manager_v1* internal;
};
struct sl_viewport {
@ -237,26 +136,44 @@ struct sl_host_surface {
struct wl_resource* resource;
struct wl_surface* proxy;
struct wp_viewport* viewport;
struct wl_buffer* proxy_buffer;
uint32_t contents_width;
uint32_t contents_height;
uint32_t contents_shm_format;
int32_t contents_scale;
int32_t contents_x_offset;
int32_t contents_y_offset;
double xdg_scale_x;
double xdg_scale_y;
bool scale_round_on_x;
bool scale_round_on_y;
struct wl_list contents_viewport;
struct sl_mmap* contents_shm_mmap;
bool contents_shaped;
pixman_region32_t contents_shape;
int has_role;
int has_output;
int has_own_scale;
int32_t cached_logical_width;
int32_t cached_logical_height;
uint32_t last_event_serial;
struct sl_output_buffer* current_buffer;
struct zwp_linux_surface_synchronization_v1* surface_sync;
struct wl_list released_buffers;
struct wl_list busy_buffers;
WeakResourcePtr<sl_host_output> output;
};
MAP_STRUCTS(wl_surface, sl_host_surface);
struct sl_host_region {
struct sl_context* ctx;
struct wl_resource* resource;
struct wl_region* proxy;
};
MAP_STRUCTS(wl_region, sl_host_region);
struct sl_host_buffer {
struct sl_context* ctx;
struct wl_resource* resource;
struct wl_buffer* proxy;
uint32_t width;
@ -264,6 +181,7 @@ struct sl_host_buffer {
struct sl_mmap* shm_mmap;
uint32_t shm_format;
struct sl_sync_point* sync_point;
bool is_drm;
};
struct sl_data_source_send_request {
@ -290,19 +208,30 @@ struct sl_output {
uint32_t id;
uint32_t version;
struct sl_global* host_global;
struct sl_host_output* host_output;
struct wl_list link;
};
struct sl_xdg_output_manager {
struct sl_context* ctx;
uint32_t id;
uint32_t version;
struct zxdg_output_manager_v1* internal;
};
struct sl_host_output {
struct sl_context* ctx;
struct wl_resource* resource;
struct wl_output* proxy;
struct zxdg_output_v1* zxdg_output;
struct zaura_output* aura_output;
int internal;
int x;
int y;
int physical_width;
int physical_height;
int scaled_physical_width;
int scaled_physical_height;
int subpixel;
char* make;
char* model;
@ -316,14 +245,40 @@ struct sl_host_output {
int preferred_scale;
int device_scale_factor;
int expecting_scale;
bool expecting_logical_size;
// The scaling factors for direct mode
// virt_scale: Used to translate from physical space to virtual space
// xdg_scale: Used to translate from virtual space to logical space
//
// The logical space is defined by the host. It will be retrieved through
// the xdg_output_manager interface.
//
// All spaces, and by consequence all scale factors, will be unique to each
// particular output.
//
// For more details, see sommelier-transform.h
double virt_scale_x;
double virt_scale_y;
double xdg_scale_x;
double xdg_scale_y;
int virt_x;
int virt_y;
int32_t logical_width;
int32_t logical_height;
int32_t logical_x;
int32_t logical_y;
struct wl_list link;
};
MAP_STRUCTS(wl_output, sl_host_output);
struct sl_host_seat {
struct sl_seat* seat;
struct wl_resource* resource;
struct wl_seat* proxy;
};
MAP_STRUCTS(wl_seat, sl_host_seat);
struct sl_accelerator {
struct wl_list link;
@ -356,14 +311,27 @@ struct sl_text_input_manager {
struct sl_context* ctx;
uint32_t id;
struct sl_global* host_global;
struct zwp_text_input_manager_v1* internal;
struct sl_global* host_x11_global;
};
struct sl_text_input_extension {
struct sl_context* ctx;
uint32_t id;
struct sl_global* host_global;
};
#ifdef GAMEPAD_SUPPORT
struct sl_gaming_input_manager {
struct sl_context* ctx;
uint32_t id;
struct zcr_gaming_input_v2* internal;
};
#endif
struct sl_pointer_constraints {
struct sl_context* ctx;
uint32_t id;
struct sl_global* host_global;
struct zwp_pointer_constraints_v1* internal;
};
struct sl_viewporter {
@ -377,7 +345,7 @@ struct sl_xdg_shell {
struct sl_context* ctx;
uint32_t id;
struct sl_global* host_global;
struct zxdg_shell_v6* internal;
struct xdg_wm_base* internal;
};
struct sl_aura_shell {
@ -396,6 +364,12 @@ struct sl_linux_dmabuf {
struct zwp_linux_dmabuf_v1* internal;
};
struct sl_linux_explicit_synchronization {
struct sl_context* ctx;
uint32_t id;
struct zwp_linux_explicit_synchronization_v1* internal;
};
struct sl_global {
struct sl_context* ctx;
const struct wl_interface* interface;
@ -412,22 +386,6 @@ struct sl_host_registry {
struct wl_list link;
};
typedef void (*sl_begin_end_access_func_t)(int fd);
struct sl_mmap {
int refcount;
int fd;
void* addr;
size_t size;
size_t bpp;
size_t num_planes;
size_t offset[2];
size_t stride[2];
size_t y_ss[2];
sl_begin_end_access_func_t begin_write;
sl_begin_end_access_func_t end_write;
struct wl_resource* buffer_resource;
};
typedef void (*sl_sync_func_t)(struct sl_context* ctx,
struct sl_sync_point* sync_point);
@ -437,65 +395,30 @@ struct sl_sync_point {
sl_sync_func_t sync;
};
struct sl_config {
uint32_t serial;
uint32_t mask;
uint32_t values[5];
uint32_t states_length;
uint32_t states[3];
};
struct sl_window {
#ifdef GAMEPAD_SUPPORT
struct sl_host_gamepad {
struct sl_context* ctx;
xcb_window_t id;
xcb_window_t frame_id;
uint32_t host_surface_id;
int unpaired;
int x;
int y;
int width;
int height;
int border_width;
int depth;
int managed;
int realized;
int activated;
int maximized;
int allow_resize;
xcb_window_t transient_for;
xcb_window_t client_leader;
int decorated;
char* name;
char* clazz;
char* startup_id;
int dark_frame;
uint32_t size_flags;
int min_width;
int min_height;
int max_width;
int max_height;
struct sl_config next_config;
struct sl_config pending_config;
struct zxdg_surface_v6* xdg_surface;
struct zxdg_toplevel_v6* xdg_toplevel;
struct zxdg_popup_v6* xdg_popup;
struct zaura_surface* aura_surface;
int state;
struct libevdev* ev_dev;
struct libevdev_uinput* uinput_dev;
bool axes_quirk;
struct wl_list link;
};
#endif
struct sl_host_buffer* sl_create_host_buffer(struct wl_client* client,
struct sl_host_buffer* sl_create_host_buffer(struct sl_context* ctx,
struct wl_client* client,
uint32_t id,
struct wl_buffer* proxy,
int32_t width,
int32_t height);
struct sl_global* sl_global_create(struct sl_context* ctx,
const struct wl_interface* interface,
int version,
void* data,
wl_global_bind_func_t bind);
int32_t height,
bool is_drm);
struct sl_global* sl_compositor_global_create(struct sl_context* ctx);
void sl_compositor_init_context(struct sl_context* ctx,
struct wl_registry* registry,
uint32_t id,
uint32_t version);
size_t sl_shm_bpp_for_shm_format(uint32_t format);
@ -528,24 +451,16 @@ struct sl_global* sl_gtk_shell_global_create(struct sl_context* ctx);
struct sl_global* sl_drm_global_create(struct sl_context* ctx);
struct sl_global* sl_text_input_extension_global_create(struct sl_context* ctx);
struct sl_global* sl_text_input_manager_global_create(struct sl_context* ctx);
struct sl_global* sl_text_input_x11_global_create(struct sl_context* ctx);
struct sl_global* sl_pointer_constraints_global_create(struct sl_context* ctx);
void sl_set_display_implementation(struct sl_context* ctx);
struct sl_mmap* sl_mmap_create(int fd,
size_t size,
size_t bpp,
size_t num_planes,
size_t offset0,
size_t stride0,
size_t offset1,
size_t stride1,
size_t y_ss0,
size_t y_ss1);
struct sl_mmap* sl_mmap_ref(struct sl_mmap* map);
void sl_mmap_unref(struct sl_mmap* map);
void sl_set_display_implementation(struct sl_context* ctx,
struct wl_client* client);
struct sl_sync_point* sl_sync_point_create(int fd);
void sl_sync_point_destroy(struct sl_sync_point* sync_point);
@ -557,9 +472,48 @@ void sl_restack_windows(struct sl_context* ctx, uint32_t focus_resource_id);
void sl_roundtrip(struct sl_context* ctx);
int sl_process_pending_configure_acks(struct sl_window* window,
struct sl_host_surface* host_surface);
void sl_window_update(struct sl_window* window);
struct sl_window* sl_lookup_window(struct sl_context* ctx, xcb_window_t id);
int sl_is_our_window(struct sl_context* ctx, xcb_window_t id);
// Exported for testing
void sl_handle_destroy_notify(struct sl_context* ctx,
xcb_destroy_notify_event_t* event);
void sl_handle_reparent_notify(struct sl_context* ctx,
xcb_reparent_notify_event_t* event);
void sl_handle_map_request(struct sl_context* ctx,
xcb_map_request_event_t* event);
void sl_handle_unmap_notify(struct sl_context* ctx,
xcb_unmap_notify_event_t* event);
void sl_handle_configure_request(struct sl_context* ctx,
xcb_configure_request_event_t* event);
void sl_handle_property_notify(struct sl_context* ctx,
xcb_property_notify_event_t* event);
void sl_create_window(struct sl_context* ctx,
xcb_window_t id,
int x,
int y,
int width,
int height,
int border_width);
void sl_handle_client_message(struct sl_context* ctx,
xcb_client_message_event_t* event);
void sl_handle_focus_in(struct sl_context* ctx, xcb_focus_in_event_t* event);
uint32_t sl_drm_format_for_shm_format(int format);
int sl_shm_format_for_drm_format(uint32_t drm_format);
#ifdef GAMEPAD_SUPPORT
void sl_gaming_seat_add_listener(struct sl_context* ctx);
#endif
bool sl_client_supports_interface(const sl_context* ctx,
const wl_client* client,
const wl_interface* interface);
#define sl_array_for_each(pos, array) \
for (pos = static_cast<decltype(pos)>((array)->data); \
(const char*)pos < ((const char*)(array)->data + (array)->size); \
(pos)++)
#endif // VM_TOOLS_SOMMELIER_SOMMELIER_H_

688
sommelier/sommelier_test.cc Normal file
View File

@ -0,0 +1,688 @@
// Copyright 2021 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <ctype.h>
#include <iostream>
#include <string>
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <sys/socket.h>
#include <wayland-client.h>
#include <wayland-util.h>
#include "sommelier.h" // NOLINT(build/include_directory)
#include "virtualization/wayland_channel.h" // NOLINT(build/include_directory)
#include "aura-shell-client-protocol.h" // NOLINT(build/include_directory)
#include "xdg-shell-client-protocol.h" // NOLINT(build/include_directory)
// Help gtest print Wayland message streams on expectation failure.
//
// This is defined in the test file mostly to avoid the main program depending
// on <iostream> and <string> merely for testing purposes. Also, it doesn't
// print the entire struct, just the data buffer, so it's not a complete
// representation of the object.
std::ostream& operator<<(std::ostream& os, const WaylandSendReceive& w) {
// Partially decode the data buffer. The content of messages is not decoded,
// except their object ID and opcode.
size_t i = 0;
while (i < w.data_size) {
uint32_t object_id = *reinterpret_cast<uint32_t*>(w.data + i);
uint32_t second_word = *reinterpret_cast<uint32_t*>(w.data + i + 4);
uint16_t message_size_in_bytes = second_word >> 16;
uint16_t opcode = second_word & 0xffff;
os << "[object ID " << object_id << ", opcode " << opcode << ", length "
<< message_size_in_bytes;
uint16_t size = MIN(message_size_in_bytes, w.data_size - i);
if (size > sizeof(uint32_t) * 2) {
os << ", args=[";
for (int j = sizeof(uint32_t) * 2; j < size; ++j) {
char byte = w.data[i + j];
if (isprint(byte)) {
os << byte;
} else {
os << "\\" << static_cast<int>(byte);
}
}
os << "]";
}
os << "]";
i += message_size_in_bytes;
}
if (i != w.data_size) {
os << "[WARNING: " << (w.data_size - i) << "undecoded trailing bytes]";
}
return os;
}
namespace vm_tools {
namespace sommelier {
using ::testing::_;
using ::testing::AllOf;
using ::testing::DoAll;
using ::testing::NiceMock;
using ::testing::PrintToString;
using ::testing::Return;
using ::testing::SetArgPointee;
class MockWaylandChannel : public WaylandChannel {
public:
MockWaylandChannel() {}
MOCK_METHOD(int32_t, init, ());
MOCK_METHOD(bool, supports_dmabuf, ());
MOCK_METHOD(int32_t,
create_context,
(int& out_socket_fd)); // NOLINT(runtime/references)
MOCK_METHOD(int32_t,
create_pipe,
(int& out_pipe_fd)); // NOLINT(runtime/references)
MOCK_METHOD(int32_t, send, (const struct WaylandSendReceive& send));
MOCK_METHOD(
int32_t,
handle_channel_event,
(enum WaylandChannelEvent & event_type, // NOLINT(runtime/references)
struct WaylandSendReceive& receive, // NOLINT(runtime/references)
int& out_read_pipe)); // NOLINT(runtime/references)
MOCK_METHOD(int32_t,
allocate,
(const struct WaylandBufferCreateInfo& create_info,
struct WaylandBufferCreateOutput&
create_output)); // NOLINT(runtime/references)
MOCK_METHOD(int32_t, sync, (int dmabuf_fd, uint64_t flags));
MOCK_METHOD(int32_t,
handle_pipe,
(int read_fd,
bool readable,
bool& hang_up)); // NOLINT(runtime/references)
MOCK_METHOD(size_t, max_send_size, ());
protected:
~MockWaylandChannel() override {}
};
// Match a WaylandSendReceive buffer containing exactly one Wayland message
// with given object ID and opcode.
MATCHER_P2(ExactlyOneMessage,
object_id,
opcode,
std::string("exactly one Wayland message ") +
(negation ? "not for" : "for") + " object ID " +
PrintToString(object_id) + ", opcode " + PrintToString(opcode)) {
const struct WaylandSendReceive& send = arg;
if (send.data_size < sizeof(uint32_t) * 2) {
// Malformed packet (too short)
return false;
}
uint32_t actual_object_id = *reinterpret_cast<uint32_t*>(send.data);
uint32_t second_word = *reinterpret_cast<uint32_t*>(send.data + 4);
uint16_t message_size_in_bytes = second_word >> 16;
uint16_t actual_opcode = second_word & 0xffff;
// ID and opcode must match expectation, and we must see exactly one message
// with the indicated length.
return object_id == actual_object_id && opcode == actual_opcode &&
message_size_in_bytes == send.data_size;
};
// Match a WaylandSendReceive buffer containing a string.
// TODO(cpelling): This is currently very naive; it doesn't respect
// boundaries between messages or their arguments. Fix me.
MATCHER_P(AnyMessageContainsString,
str,
std::string("a Wayland message containing string ") + str) {
const struct WaylandSendReceive& send = arg;
size_t prefix_len = sizeof(uint32_t) * 2;
std::string data_as_str(reinterpret_cast<char*>(send.data + prefix_len),
send.data_size - prefix_len);
return data_as_str.find(str) != std::string::npos;
}
// Fixture for tests which exercise only Wayland functionality.
class WaylandTest : public ::testing::Test {
public:
void SetUp() override {
ON_CALL(mock_wayland_channel_, create_context(_)).WillByDefault(Return(0));
ON_CALL(mock_wayland_channel_, max_send_size())
.WillByDefault(Return(DEFAULT_BUFFER_SIZE));
EXPECT_CALL(mock_wayland_channel_, init).Times(1);
sl_context_init_default(&ctx);
ctx.host_display = wl_display_create();
assert(ctx.host_display);
ctx.channel = &mock_wayland_channel_;
EXPECT_TRUE(sl_context_init_wayland_channel(
&ctx, wl_display_get_event_loop(ctx.host_display), false));
InitContext();
Connect();
}
void TearDown() override {
// Process any pending messages before the test exits.
Pump();
// TODO(cpelling): Destroy context and any created windows?
}
// Flush and dispatch Wayland client calls to the mock host.
//
// Called by default in TearDown(), but you can also trigger it midway
// through the test.
//
// If you call `EXPECT_CALL(mock_wayland_channel_, send)` before Pump(), the
// expectations won't trigger until the Pump() call.
//
// Conversely, calling Pump() before
// `EXPECT_CALL(mock_wayland_channel_, send)` is useful to flush out
// init messages not relevant to your test case.
void Pump() {
wl_display_flush(ctx.display);
wl_event_loop_dispatch(wl_display_get_event_loop(ctx.host_display), 0);
}
protected:
// Allow subclasses to customize the context prior to Connect().
virtual void InitContext() {}
// Set up the Wayland connection, compositor and registry.
virtual void Connect() {
ctx.display = wl_display_connect_to_fd(ctx.virtwl_display_fd);
wl_registry* registry = wl_display_get_registry(ctx.display);
sl_compositor_init_context(&ctx, registry, 0, kMinHostWlCompositorVersion);
EXPECT_NE(ctx.compositor, nullptr);
// Fake the Wayland server advertising globals.
uint32_t id = 1;
sl_registry_handler(&ctx, registry, id++, "xdg_wm_base",
XDG_WM_BASE_GET_XDG_SURFACE_SINCE_VERSION);
sl_registry_handler(&ctx, registry, id++, "zaura_shell",
ZAURA_SURFACE_SET_FULLSCREEN_MODE_SINCE_VERSION);
}
testing::NiceMock<MockWaylandChannel> mock_wayland_channel_;
sl_context ctx;
};
// Fixture for unit tests which exercise both Wayland and X11 functionality.
class X11Test : public WaylandTest {
public:
void InitContext() override {
WaylandTest::InitContext();
ctx.xwayland = 1;
}
void Connect() override {
WaylandTest::Connect();
ctx.connection = xcb_connect(NULL, NULL);
}
virtual sl_window* CreateWindowWithoutRole() {
xcb_window_t window_id = 1;
sl_create_window(&ctx, window_id, 0, 0, 800, 600, 0);
sl_window* window = sl_lookup_window(&ctx, window_id);
EXPECT_NE(window, nullptr);
return window;
}
virtual sl_window* CreateToplevelWindow() {
sl_window* window = CreateWindowWithoutRole();
wl_surface* surface =
wl_compositor_create_surface(ctx.compositor->internal);
window->host_surface_id =
wl_proxy_get_id(reinterpret_cast<wl_proxy*>(surface));
window->xdg_surface =
xdg_wm_base_get_xdg_surface(ctx.xdg_shell->internal, surface);
window->xdg_toplevel = xdg_surface_get_toplevel(window->xdg_surface);
window->aura_surface =
zaura_shell_get_aura_surface(ctx.aura_shell->internal, surface);
return window;
}
};
namespace {
uint32_t XdgToplevelId(sl_window* window) {
return wl_proxy_get_id(reinterpret_cast<wl_proxy*>(window->xdg_toplevel));
}
uint32_t AuraSurfaceId(sl_window* window) {
return wl_proxy_get_id(reinterpret_cast<wl_proxy*>(window->aura_surface));
}
} // namespace
TEST_F(WaylandTest, CanCommitToEmptySurface) {
wl_surface* surface = wl_compositor_create_surface(ctx.compositor->internal);
wl_surface_commit(surface);
}
TEST_F(X11Test, TogglesFullscreenOnWmStateFullscreen) {
// Arrange: Create an xdg_toplevel surface. Initially it's not fullscreen.
sl_window* window = CreateToplevelWindow();
uint32_t xdg_toplevel_id = XdgToplevelId(window);
EXPECT_EQ(window->fullscreen, 0);
Pump(); // exclude pending messages from EXPECT_CALL()s below
// Act: Pretend the window is owned by an X11 client requesting fullscreen.
// Sommelier receives the XCB_CLIENT_MESSAGE request due to its role as the
// X11 window manager. For test purposes, we skip creating a real X11
// connection and just call directly into the relevant handler.
xcb_client_message_event_t event;
event.response_type = XCB_CLIENT_MESSAGE;
event.format = 32;
event.window = window->id;
event.type = ctx.atoms[ATOM_NET_WM_STATE].value;
event.data.data32[0] = NET_WM_STATE_ADD;
event.data.data32[1] = ctx.atoms[ATOM_NET_WM_STATE_FULLSCREEN].value;
event.data.data32[2] = 0;
event.data.data32[3] = 0;
event.data.data32[4] = 0;
sl_handle_client_message(&ctx, &event);
// Assert: Sommelier records the fullscreen state.
EXPECT_EQ(window->fullscreen, 1);
// Assert: Sommelier forwards the fullscreen request to Exo.
EXPECT_CALL(
mock_wayland_channel_,
send(ExactlyOneMessage(xdg_toplevel_id, XDG_TOPLEVEL_SET_FULLSCREEN)))
.RetiresOnSaturation();
Pump();
// Act: Pretend the fictitious X11 client requests non-fullscreen.
event.data.data32[0] = NET_WM_STATE_REMOVE;
sl_handle_client_message(&ctx, &event);
// Assert: Sommelier records the fullscreen state.
EXPECT_EQ(window->fullscreen, 0);
// Assert: Sommelier forwards the unfullscreen request to Exo.
EXPECT_CALL(
mock_wayland_channel_,
send(ExactlyOneMessage(xdg_toplevel_id, XDG_TOPLEVEL_UNSET_FULLSCREEN)))
.RetiresOnSaturation();
}
TEST_F(X11Test, TogglesMaximizeOnWmStateMaximize) {
// Arrange: Create an xdg_toplevel surface. Initially it's not maximized.
sl_window* window = CreateToplevelWindow();
uint32_t xdg_toplevel_id = XdgToplevelId(window);
EXPECT_EQ(window->maximized, 0);
Pump(); // exclude pending messages from EXPECT_CALL()s below
// Act: Pretend an X11 client owns the surface, and requests to maximize it.
xcb_client_message_event_t event;
event.response_type = XCB_CLIENT_MESSAGE;
event.format = 32;
event.window = window->id;
event.type = ctx.atoms[ATOM_NET_WM_STATE].value;
event.data.data32[0] = NET_WM_STATE_ADD;
event.data.data32[1] = ctx.atoms[ATOM_NET_WM_STATE_MAXIMIZED_HORZ].value;
event.data.data32[2] = ctx.atoms[ATOM_NET_WM_STATE_MAXIMIZED_VERT].value;
event.data.data32[3] = 0;
event.data.data32[4] = 0;
sl_handle_client_message(&ctx, &event);
// Assert: Sommelier records the maximized state + forwards to Exo.
EXPECT_EQ(window->maximized, 1);
EXPECT_CALL(
mock_wayland_channel_,
send(ExactlyOneMessage(xdg_toplevel_id, XDG_TOPLEVEL_SET_MAXIMIZED)))
.RetiresOnSaturation();
Pump();
// Act: Pretend the fictitious X11 client requests to unmaximize.
event.data.data32[0] = NET_WM_STATE_REMOVE;
sl_handle_client_message(&ctx, &event);
// Assert: Sommelier records the unmaximized state + forwards to Exo.
EXPECT_EQ(window->maximized, 0);
EXPECT_CALL(
mock_wayland_channel_,
send(ExactlyOneMessage(xdg_toplevel_id, XDG_TOPLEVEL_UNSET_MAXIMIZED)))
.RetiresOnSaturation();
Pump();
}
TEST_F(X11Test, CanEnterFullscreenIfAlreadyMaximized) {
// Arrange
sl_window* window = CreateToplevelWindow();
uint32_t xdg_toplevel_id = XdgToplevelId(window);
Pump(); // exclude pending messages from EXPECT_CALL()s below
// Act: Pretend an X11 client owns the surface, and requests to maximize it.
xcb_client_message_event_t event;
event.response_type = XCB_CLIENT_MESSAGE;
event.format = 32;
event.window = window->id;
event.type = ctx.atoms[ATOM_NET_WM_STATE].value;
event.data.data32[0] = NET_WM_STATE_ADD;
event.data.data32[1] = ctx.atoms[ATOM_NET_WM_STATE_MAXIMIZED_HORZ].value;
event.data.data32[2] = ctx.atoms[ATOM_NET_WM_STATE_MAXIMIZED_VERT].value;
event.data.data32[3] = 0;
event.data.data32[4] = 0;
sl_handle_client_message(&ctx, &event);
// Assert: Sommelier records the maximized state + forwards to Exo.
EXPECT_EQ(window->maximized, 1);
EXPECT_CALL(
mock_wayland_channel_,
send(ExactlyOneMessage(xdg_toplevel_id, XDG_TOPLEVEL_SET_MAXIMIZED)))
.RetiresOnSaturation();
Pump();
// Act: Pretend the X11 client requests fullscreen.
xcb_client_message_event_t fsevent;
fsevent.response_type = XCB_CLIENT_MESSAGE;
fsevent.format = 32;
fsevent.window = window->id;
fsevent.type = ctx.atoms[ATOM_NET_WM_STATE].value;
fsevent.data.data32[0] = NET_WM_STATE_ADD;
fsevent.data.data32[1] = 0;
fsevent.data.data32[2] = ctx.atoms[ATOM_NET_WM_STATE_FULLSCREEN].value;
fsevent.data.data32[3] = 0;
fsevent.data.data32[4] = 0;
sl_handle_client_message(&ctx, &fsevent);
// Assert: Sommelier records the fullscreen state + forwards to Exo.
EXPECT_EQ(window->fullscreen, 1);
EXPECT_CALL(
mock_wayland_channel_,
send(ExactlyOneMessage(xdg_toplevel_id, XDG_TOPLEVEL_SET_FULLSCREEN)))
.RetiresOnSaturation();
Pump();
}
TEST_F(X11Test, UpdatesApplicationIdFromContext) {
sl_window* window = CreateToplevelWindow();
Pump();
window->managed = 1; // pretend window is mapped
// Should be ignored; global app id from context takes priority.
window->app_id_property = "org.chromium.guest_os.termina.appid.from.window";
ctx.application_id = "org.chromium.guest_os.termina.appid.from.context";
sl_update_application_id(&ctx, window);
EXPECT_CALL(mock_wayland_channel_,
send(AllOf(ExactlyOneMessage(AuraSurfaceId(window),
ZAURA_SURFACE_SET_APPLICATION_ID),
AnyMessageContainsString(ctx.application_id))))
.RetiresOnSaturation();
Pump();
}
TEST_F(X11Test, UpdatesApplicationIdFromWindow) {
sl_window* window = CreateToplevelWindow();
Pump();
window->managed = 1; // pretend window is mapped
window->app_id_property = "org.chromium.guest_os.termina.appid.from.window";
sl_update_application_id(&ctx, window);
EXPECT_CALL(mock_wayland_channel_,
send(AllOf(ExactlyOneMessage(AuraSurfaceId(window),
ZAURA_SURFACE_SET_APPLICATION_ID),
AnyMessageContainsString(window->app_id_property))))
.RetiresOnSaturation();
Pump();
}
TEST_F(X11Test, UpdatesApplicationIdFromWindowClass) {
sl_window* window = CreateToplevelWindow();
Pump();
window->managed = 1; // pretend window is mapped
window->clazz = strdup("very_classy"); // not const, can't use a literal
ctx.vm_id = "testvm";
sl_update_application_id(&ctx, window);
EXPECT_CALL(
mock_wayland_channel_,
send(AllOf(ExactlyOneMessage(AuraSurfaceId(window),
ZAURA_SURFACE_SET_APPLICATION_ID),
AnyMessageContainsString(
"org.chromium.guest_os.testvm.wmclass.very_classy"))))
.RetiresOnSaturation();
Pump();
free(window->clazz);
}
TEST_F(X11Test, UpdatesApplicationIdFromClientLeader) {
sl_window* window = CreateToplevelWindow();
Pump();
window->managed = 1; // pretend window is mapped
window->client_leader = window->id;
ctx.vm_id = "testvm";
sl_update_application_id(&ctx, window);
EXPECT_CALL(mock_wayland_channel_,
send(AllOf(ExactlyOneMessage(AuraSurfaceId(window),
ZAURA_SURFACE_SET_APPLICATION_ID),
AnyMessageContainsString(
"org.chromium.guest_os.testvm.wmclientleader."))))
.RetiresOnSaturation();
Pump();
}
TEST_F(X11Test, UpdatesApplicationIdFromXid) {
sl_window* window = CreateToplevelWindow();
Pump();
window->managed = 1; // pretend window is mapped
ctx.vm_id = "testvm";
sl_update_application_id(&ctx, window);
EXPECT_CALL(mock_wayland_channel_,
send(AllOf(ExactlyOneMessage(AuraSurfaceId(window),
ZAURA_SURFACE_SET_APPLICATION_ID),
AnyMessageContainsString(
"org.chromium.guest_os.testvm.xid."))))
.RetiresOnSaturation();
Pump();
}
TEST_F(X11Test, NonExistentWindowDoesNotCrash) {
// This test is testing cases where sl_lookup_window returns NULL
// sl_handle_destroy_notify
xcb_destroy_notify_event_t destroy_event;
// Arrange: Use a window that does not exist.
destroy_event.window = 123;
// Act/Assert: Sommelier does not crash.
sl_handle_destroy_notify(&ctx, &destroy_event);
// sl_handle_client_message
xcb_client_message_event_t message_event;
message_event.window = 123;
message_event.type = ctx.atoms[ATOM_WL_SURFACE_ID].value;
sl_handle_client_message(&ctx, &message_event);
message_event.type = ctx.atoms[ATOM_NET_ACTIVE_WINDOW].value;
sl_handle_client_message(&ctx, &message_event);
message_event.type = ctx.atoms[ATOM_NET_WM_MOVERESIZE].value;
sl_handle_client_message(&ctx, &message_event);
message_event.type = ctx.atoms[ATOM_NET_WM_STATE].value;
sl_handle_client_message(&ctx, &message_event);
message_event.type = ctx.atoms[ATOM_WM_CHANGE_STATE].value;
sl_handle_client_message(&ctx, &message_event);
// sl_handle_map_request
xcb_map_request_event_t map_event;
map_event.window = 123;
sl_handle_map_request(&ctx, &map_event);
// sl_handle_unmap_notify
xcb_unmap_notify_event_t unmap_event;
unmap_event.window = 123;
sl_handle_unmap_notify(&ctx, &unmap_event);
// sl_handle_configure_request
xcb_configure_request_event_t configure_event;
configure_event.window = 123;
sl_handle_configure_request(&ctx, &configure_event);
// sl_handle_focus_in
xcb_focus_in_event_t focus_event;
focus_event.event = 123;
sl_handle_focus_in(&ctx, &focus_event);
// sl_handle_property_notify
xcb_property_notify_event_t notify_event;
notify_event.window = 123;
notify_event.atom = XCB_ATOM_WM_NAME;
sl_handle_property_notify(&ctx, &notify_event);
notify_event.atom = XCB_ATOM_WM_CLASS;
sl_handle_property_notify(&ctx, &notify_event);
notify_event.atom = ctx.application_id_property_atom;
sl_handle_property_notify(&ctx, &notify_event);
notify_event.atom = XCB_ATOM_WM_NORMAL_HINTS;
sl_handle_property_notify(&ctx, &notify_event);
notify_event.atom = XCB_ATOM_WM_HINTS;
sl_handle_property_notify(&ctx, &notify_event);
notify_event.atom = ATOM_MOTIF_WM_HINTS;
sl_handle_property_notify(&ctx, &notify_event);
notify_event.atom = ATOM_GTK_THEME_VARIANT;
sl_handle_property_notify(&ctx, &notify_event);
// sl_handle_reparent_notify
// Put this one last and used a different window id as it creates a window.
xcb_reparent_notify_event_t reparent_event;
reparent_event.window = 1234;
xcb_screen_t screen;
screen.root = 1234;
ctx.screen = &screen;
reparent_event.parent = ctx.screen->root;
reparent_event.x = 0;
reparent_event.y = 0;
sl_handle_reparent_notify(&ctx, &reparent_event);
}
#ifdef BLACK_SCREEN_FIX
TEST_F(X11Test, IconifySuppressesFullscreen) {
// Arrange: Create an xdg_toplevel surface. Initially it's not iconified.
sl_window* window = CreateToplevelWindow();
uint32_t xdg_toplevel_id = XdgToplevelId(window);
EXPECT_EQ(window->iconified, 0);
// Act: Pretend an X11 client owns the surface, and requests to iconify it.
xcb_client_message_event_t event;
event.response_type = XCB_CLIENT_MESSAGE;
event.format = 32;
event.window = window->id;
event.type = ctx.atoms[ATOM_WM_CHANGE_STATE].value;
event.data.data32[0] = WM_STATE_ICONIC;
sl_handle_client_message(&ctx, &event);
Pump();
// Assert: Sommelier records the iconified state.
EXPECT_EQ(window->iconified, 1);
// Act: Pretend the surface is requested to be fullscreened.
event.type = ctx.atoms[ATOM_NET_WM_STATE].value;
event.data.data32[0] = NET_WM_STATE_ADD;
event.data.data32[1] = ctx.atoms[ATOM_NET_WM_STATE_FULLSCREEN].value;
event.data.data32[2] = 0;
event.data.data32[3] = 0;
event.data.data32[4] = 0;
sl_handle_client_message(&ctx, &event);
// Assert: Sommelier should not send the fullscreen call as we are iconified.
EXPECT_CALL(
mock_wayland_channel_,
send((ExactlyOneMessage(xdg_toplevel_id, XDG_TOPLEVEL_SET_FULLSCREEN))))
.Times(0);
Pump();
// Act: Pretend the surface receives focus.
xcb_focus_in_event_t focus_event;
focus_event.response_type = XCB_FOCUS_IN;
focus_event.event = window->id;
sl_handle_focus_in(&ctx, &focus_event);
// Assert: The window is deiconified.
EXPECT_EQ(window->iconified, 0);
// Assert: Sommelier should now send the fullscreen call.
EXPECT_CALL(
mock_wayland_channel_,
send((ExactlyOneMessage(xdg_toplevel_id, XDG_TOPLEVEL_SET_FULLSCREEN))))
.Times(1);
Pump();
}
TEST_F(X11Test, IconifySuppressesUnmaximize) {
// Arrange: Create an xdg_toplevel surface. Initially it's not iconified.
sl_window* window = CreateToplevelWindow();
uint32_t xdg_toplevel_id = XdgToplevelId(window);
EXPECT_EQ(window->iconified, 0);
// Arrange: Maximize it.
xcb_client_message_event_t event;
event.response_type = XCB_CLIENT_MESSAGE;
event.format = 32;
event.window = window->id;
event.type = ctx.atoms[ATOM_NET_WM_STATE].value;
event.data.data32[0] = NET_WM_STATE_ADD;
event.data.data32[1] = ctx.atoms[ATOM_NET_WM_STATE_MAXIMIZED_VERT].value;
event.data.data32[2] = ctx.atoms[ATOM_NET_WM_STATE_MAXIMIZED_HORZ].value;
event.data.data32[3] = 0;
event.data.data32[4] = 0;
sl_handle_client_message(&ctx, &event);
EXPECT_EQ(window->maximized, 1);
// Act: Pretend an X11 client owns the surface, and requests to iconify it.
event.type = ctx.atoms[ATOM_WM_CHANGE_STATE].value;
event.data.data32[0] = WM_STATE_ICONIC;
sl_handle_client_message(&ctx, &event);
Pump();
// Assert: Sommelier records the iconified state.
EXPECT_EQ(window->iconified, 1);
// Act: Pretend the surface is requested to be unmaximized.
event.type = ctx.atoms[ATOM_NET_WM_STATE].value;
event.data.data32[0] = NET_WM_STATE_REMOVE;
event.data.data32[1] = ctx.atoms[ATOM_NET_WM_STATE_MAXIMIZED_VERT].value;
event.data.data32[2] = ctx.atoms[ATOM_NET_WM_STATE_MAXIMIZED_HORZ].value;
event.data.data32[3] = 0;
event.data.data32[4] = 0;
sl_handle_client_message(&ctx, &event);
// Assert: Sommelier should not send the unmiximize call as we are iconified.
EXPECT_CALL(
mock_wayland_channel_,
send((ExactlyOneMessage(xdg_toplevel_id, XDG_TOPLEVEL_UNSET_MAXIMIZED))))
.Times(0);
Pump();
// Act: Pretend the surface receives focus.
xcb_focus_in_event_t focus_event;
focus_event.response_type = XCB_FOCUS_IN;
focus_event.event = window->id;
sl_handle_focus_in(&ctx, &focus_event);
// Assert: The window is deiconified.
EXPECT_EQ(window->iconified, 0);
// Assert: Sommelier should now send the unmiximize call.
EXPECT_CALL(
mock_wayland_channel_,
send((ExactlyOneMessage(xdg_toplevel_id, XDG_TOPLEVEL_UNSET_MAXIMIZED))))
.Times(1);
Pump();
}
#endif
} // namespace sommelier
} // namespace vm_tools
int main(int argc, char** argv) {
testing::InitGoogleTest(&argc, argv);
testing::GTEST_FLAG(throw_on_failure) = true;
// TODO(nverne): set up logging?
return RUN_ALL_TESTS();
}

View File

@ -0,0 +1,2 @@
ryanneph@google.com
zzyiwei@google.com

View File

@ -0,0 +1,279 @@
/*
* Copyright 2013 Red Hat
* All Rights Reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef VIRTGPU_DRM_H
#define VIRTGPU_DRM_H
#include "drm.h"
#if defined(__cplusplus)
extern "C" {
#endif
/* Please note that modifications to all structs defined here are
* subject to backwards-compatibility constraints.
*
* Do not use pointers, use __u64 instead for 32 bit / 64 bit user/kernel
* compatibility Keep fields aligned to their size
*/
#define DRM_VIRTGPU_MAP 0x01
#define DRM_VIRTGPU_EXECBUFFER 0x02
#define DRM_VIRTGPU_GETPARAM 0x03
#define DRM_VIRTGPU_RESOURCE_CREATE 0x04
#define DRM_VIRTGPU_RESOURCE_INFO 0x05
#define DRM_VIRTGPU_TRANSFER_FROM_HOST 0x06
#define DRM_VIRTGPU_TRANSFER_TO_HOST 0x07
#define DRM_VIRTGPU_WAIT 0x08
#define DRM_VIRTGPU_GET_CAPS 0x09
#define DRM_VIRTGPU_RESOURCE_CREATE_BLOB 0x0a
#define DRM_VIRTGPU_CONTEXT_INIT 0x0b
#define VIRTGPU_EXECBUF_FENCE_FD_IN 0x01
#define VIRTGPU_EXECBUF_FENCE_FD_OUT 0x02
#define VIRTGPU_EXECBUF_RING_IDX 0x04
#define VIRTGPU_EXECBUF_FLAGS \
(VIRTGPU_EXECBUF_FENCE_FD_IN | VIRTGPU_EXECBUF_FENCE_FD_OUT | \
VIRTGPU_EXECBUF_RING_IDX | 0)
struct drm_virtgpu_map {
__u64 offset; /* use for mmap system call */
__u32 handle;
__u32 pad;
};
struct drm_virtgpu_execbuffer {
__u32 flags;
__u32 size;
__u64 command; /* void* */
__u64 bo_handles;
__u32 num_bo_handles;
__s32 fence_fd; /* in/out fence fd (see VIRTGPU_EXECBUF_FENCE_FD_IN/OUT) */
__u32 ring_idx; /* which command ring to use for fence creation */
__u32 pad;
};
#define VIRTGPU_PARAM_3D_FEATURES 1 /* do we have 3D features in the hw */
#define VIRTGPU_PARAM_CAPSET_QUERY_FIX 2 /* do we have the capset fix */
#define VIRTGPU_PARAM_RESOURCE_BLOB 3 /* DRM_VIRTGPU_RESOURCE_CREATE_BLOB */
#define VIRTGPU_PARAM_HOST_VISIBLE 4 /* Host blob resources are mappable */
#define VIRTGPU_PARAM_CROSS_DEVICE 5 /* Cross virtio-device resource sharing */
#define VIRTGPU_PARAM_CONTEXT_INIT 6 /* DRM_VIRTGPU_CONTEXT_INIT */
#define VIRTGPU_PARAM_SUPPORTED_CAPSET_IDs 7 /* Bitmask of supported capability set ids */
struct drm_virtgpu_getparam {
__u64 param;
__u64 value;
};
/* NO_BO flags? NO resource flag? */
/* resource flag for y_0_top */
struct drm_virtgpu_resource_create {
__u32 target;
__u32 format;
__u32 bind;
__u32 width;
__u32 height;
__u32 depth;
__u32 array_size;
__u32 last_level;
__u32 nr_samples;
__u32 flags;
__u32 bo_handle; /* if this is set - recreate a new resource attached to this bo ? */
__u32 res_handle; /* returned by kernel */
__u32 size; /* validate transfer in the host */
__u32 stride; /* validate transfer in the host */
};
struct drm_virtgpu_resource_info {
__u32 bo_handle;
__u32 res_handle;
__u32 size;
__u32 blob_mem;
};
/* CHROMIUM */
struct drm_virtgpu_resource_info_cros {
__u32 bo_handle;
__u32 res_handle;
__u32 size;
/* Return res_handle and size. Return extended info (strides, num_planes,
* etc.) until chromeos-5.4 and return blob_mem since chromeos-5.10.
*/
#define VIRTGPU_RESOURCE_INFO_TYPE_DEFAULT 0
/* Return res_handle, size, and extended info */
#define VIRTGPU_RESOURCE_INFO_TYPE_EXTENDED 1
union {
__u32 type; /* in, VIRTGPU_RESOURCE_INFO_TYPE_* */
__u32 blob_mem;
__u32 stride;
__u32 strides[4]; /* strides[0] is accessible with stride. */
};
__u32 num_planes;
__u32 offsets[4];
__u64 format_modifier;
};
struct drm_virtgpu_3d_box {
__u32 x;
__u32 y;
__u32 z;
__u32 w;
__u32 h;
__u32 d;
};
struct drm_virtgpu_3d_transfer_to_host {
__u32 bo_handle;
struct drm_virtgpu_3d_box box;
__u32 level;
__u32 offset;
__u32 stride;
__u32 layer_stride;
};
struct drm_virtgpu_3d_transfer_from_host {
__u32 bo_handle;
struct drm_virtgpu_3d_box box;
__u32 level;
__u32 offset;
__u32 stride;
__u32 layer_stride;
};
#define VIRTGPU_WAIT_NOWAIT 1 /* like it */
struct drm_virtgpu_3d_wait {
__u32 handle; /* 0 is an invalid handle */
__u32 flags;
};
struct drm_virtgpu_get_caps {
__u32 cap_set_id;
__u32 cap_set_ver;
__u64 addr;
__u32 size;
__u32 pad;
};
struct drm_virtgpu_resource_create_blob {
#define VIRTGPU_BLOB_MEM_GUEST 0x0001
#define VIRTGPU_BLOB_MEM_HOST3D 0x0002
#define VIRTGPU_BLOB_MEM_HOST3D_GUEST 0x0003
#define VIRTGPU_BLOB_FLAG_USE_MAPPABLE 0x0001
#define VIRTGPU_BLOB_FLAG_USE_SHAREABLE 0x0002
#define VIRTGPU_BLOB_FLAG_USE_CROSS_DEVICE 0x0004
/* zero is invalid blob_mem */
__u32 blob_mem;
__u32 blob_flags;
__u32 bo_handle;
__u32 res_handle;
__u64 size;
/*
* for 3D contexts with VIRTGPU_BLOB_MEM_HOST3D_GUEST and
* VIRTGPU_BLOB_MEM_HOST3D otherwise, must be zero.
*/
__u32 pad;
__u32 cmd_size;
__u64 cmd;
__u64 blob_id;
};
#define VIRTGPU_CONTEXT_PARAM_CAPSET_ID 0x0001
#define VIRTGPU_CONTEXT_PARAM_NUM_RINGS 0x0002
#define VIRTGPU_CONTEXT_PARAM_POLL_RINGS_MASK 0x0003
struct drm_virtgpu_context_set_param {
__u64 param;
__u64 value;
};
struct drm_virtgpu_context_init {
__u32 num_params;
__u32 pad;
/* pointer to drm_virtgpu_context_set_param array */
__u64 ctx_set_params;
};
/*
* Event code that's given when VIRTGPU_CONTEXT_PARAM_POLL_RINGS_MASK is in
* effect. The event size is sizeof(drm_event), since there is no additional
* payload.
*/
#define VIRTGPU_EVENT_FENCE_SIGNALED 0x90000000
#define DRM_IOCTL_VIRTGPU_MAP \
DRM_IOWR(DRM_COMMAND_BASE + DRM_VIRTGPU_MAP, struct drm_virtgpu_map)
#define DRM_IOCTL_VIRTGPU_EXECBUFFER \
DRM_IOWR(DRM_COMMAND_BASE + DRM_VIRTGPU_EXECBUFFER,\
struct drm_virtgpu_execbuffer)
#define DRM_IOCTL_VIRTGPU_GETPARAM \
DRM_IOWR(DRM_COMMAND_BASE + DRM_VIRTGPU_GETPARAM,\
struct drm_virtgpu_getparam)
#define DRM_IOCTL_VIRTGPU_RESOURCE_CREATE \
DRM_IOWR(DRM_COMMAND_BASE + DRM_VIRTGPU_RESOURCE_CREATE, \
struct drm_virtgpu_resource_create)
#define DRM_IOCTL_VIRTGPU_RESOURCE_INFO \
DRM_IOWR(DRM_COMMAND_BASE + DRM_VIRTGPU_RESOURCE_INFO, \
struct drm_virtgpu_resource_info)
/* same ioctl number as DRM_IOCTL_VIRTGPU_RESOURCE_INFO */
#define DRM_IOCTL_VIRTGPU_RESOURCE_INFO_CROS \
DRM_IOWR(DRM_COMMAND_BASE + DRM_VIRTGPU_RESOURCE_INFO, \
struct drm_virtgpu_resource_info_cros)
#define DRM_IOCTL_VIRTGPU_TRANSFER_FROM_HOST \
DRM_IOWR(DRM_COMMAND_BASE + DRM_VIRTGPU_TRANSFER_FROM_HOST, \
struct drm_virtgpu_3d_transfer_from_host)
#define DRM_IOCTL_VIRTGPU_TRANSFER_TO_HOST \
DRM_IOWR(DRM_COMMAND_BASE + DRM_VIRTGPU_TRANSFER_TO_HOST, \
struct drm_virtgpu_3d_transfer_to_host)
#define DRM_IOCTL_VIRTGPU_WAIT \
DRM_IOWR(DRM_COMMAND_BASE + DRM_VIRTGPU_WAIT, \
struct drm_virtgpu_3d_wait)
#define DRM_IOCTL_VIRTGPU_GET_CAPS \
DRM_IOWR(DRM_COMMAND_BASE + DRM_VIRTGPU_GET_CAPS, \
struct drm_virtgpu_get_caps)
#define DRM_IOCTL_VIRTGPU_RESOURCE_CREATE_BLOB \
DRM_IOWR(DRM_COMMAND_BASE + DRM_VIRTGPU_RESOURCE_CREATE_BLOB, \
struct drm_virtgpu_resource_create_blob)
#define DRM_IOCTL_VIRTGPU_CONTEXT_INIT \
DRM_IOWR(DRM_COMMAND_BASE + DRM_VIRTGPU_CONTEXT_INIT, \
struct drm_virtgpu_context_init)
#if defined(__cplusplus)
}
#endif
#endif

View File

@ -21,6 +21,7 @@ enum virtwl_ioctl_new_type {
VIRTWL_IOCTL_NEW_PIPE_WRITE,
/* create a new virtwl dmabuf that is writable via the returned fd */
VIRTWL_IOCTL_NEW_DMABUF,
VIRTWL_IOCTL_NEW_CTX_NAMED, /* open a new named connection context */
};
struct virtwl_ioctl_new {
@ -42,6 +43,8 @@ struct virtwl_ioctl_new {
__u32 offset1; /* return offset1 */
__u32 offset2; /* return offset2 */
} dmabuf;
/* name of socket if type == VIRTIO_WL_CMD_VFD_NEW_CTX_NAMED */
char name[32];
};
};

View File

@ -0,0 +1,839 @@
// Copyright 2021 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <errno.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <xf86drm.h>
#include <gbm.h>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include "virtgpu_cross_domain_protocol.h" // NOLINT(build/include_directory)
#include "linux-headers/virtgpu_drm.h" // NOLINT(build/include_directory)
#include "wayland_channel.h" // NOLINT(build/include_directory)
// The size of a page for the guest kernel
#define PAGE_SIZE (getpagesize())
// We require six virtgpu params to use the virtgpu channel
#define REQUIRED_PARAMS_SIZE 6
// The capset for the virtgpu cross domain context type (defined internally
// for now)
#define CAPSET_CROSS_DOMAIN 5
// Constants taken from pipe_loader_drm.c in Mesa
#define DRM_NUM_NODES 63
// DRM Render nodes start at 128
#define DRM_RENDER_NODE_START 128
#define MAX_SEND_SIZE \
(DEFAULT_BUFFER_SIZE - sizeof(struct CrossDomainSendReceive))
#define MAX_WRITE_SIZE \
(DEFAULT_BUFFER_SIZE - sizeof(struct CrossDomainReadWrite))
struct virtgpu_param {
uint64_t param;
const char* name;
uint32_t value;
};
#define PARAM(x) \
(struct virtgpu_param) { x, #x, 0 }
int open_virtgpu(char** drm_device) {
int fd;
char* node;
drmVersionPtr drm_version;
uint32_t num_nodes = DRM_NUM_NODES;
uint32_t min_render_node = DRM_RENDER_NODE_START;
uint32_t max_render_node = (min_render_node + num_nodes);
for (uint32_t idx = min_render_node; idx < max_render_node; idx++) {
if (asprintf(&node, "%s/renderD%d", DRM_DIR_NAME, idx) < 0)
continue;
fd = open(node, O_RDWR | O_CLOEXEC);
if (fd < 0) {
free(node);
continue;
}
drm_version = drmGetVersion(fd);
if (!drm_version) {
free(node);
close(fd);
continue;
}
if (!strcmp(drm_version->name, "virtio_gpu")) {
drmFreeVersion(drm_version);
*drm_device = strdup(node);
free(node);
return fd;
}
drmFreeVersion(drm_version);
free(node);
close(fd);
}
return -1;
}
int32_t fstat_pipe(int fd, uint32_t& inode) {
int32_t ret;
struct stat statbuf = {0};
ret = fstat(fd, &statbuf);
if (ret) {
fprintf(stderr, "fstat failed\n");
return ret;
}
// fstat + S_ISFIFO(..) will return true for both anonymous and named pipes.
if (!S_ISFIFO(statbuf.st_mode)) {
fprintf(stderr, "expected anonymous pipe\n");
return -EINVAL;
}
inode = statbuf.st_ino;
return 0;
}
VirtGpuChannel::~VirtGpuChannel() {
if (ring_addr_ != MAP_FAILED)
munmap(ring_addr_, PAGE_SIZE);
// An unwritten rule for the DRM subsystem is a valid GEM valid must be
// non-zero. Checkout drm_gem_handle_create_tail in the kernel.
if (ring_handle_)
close_gem_handle(ring_handle_);
if (virtgpu_ >= 0)
close(virtgpu_);
}
int32_t VirtGpuChannel::init() {
int32_t ret;
char* drm_device = NULL;
uint32_t supports_wayland;
struct drm_virtgpu_get_caps args = {0};
struct CrossDomainCapabilities cross_domain_caps = {0};
virtgpu_ = open_virtgpu(&drm_device);
if (virtgpu_ < 0) {
fprintf(stderr, "failed to open virtgpu\n");
return -errno;
}
// Not needed by the VirtGpuChannel.
free(drm_device);
struct virtgpu_param params[REQUIRED_PARAMS_SIZE] = {
PARAM(VIRTGPU_PARAM_3D_FEATURES),
PARAM(VIRTGPU_PARAM_CAPSET_QUERY_FIX),
PARAM(VIRTGPU_PARAM_RESOURCE_BLOB),
PARAM(VIRTGPU_PARAM_HOST_VISIBLE),
PARAM(VIRTGPU_PARAM_CONTEXT_INIT),
PARAM(VIRTGPU_PARAM_SUPPORTED_CAPSET_IDs),
};
for (uint32_t i = 0; i < REQUIRED_PARAMS_SIZE; i++) {
struct drm_virtgpu_getparam get_param = {0};
get_param.param = params[i].param;
get_param.value = (uint64_t)(uintptr_t)&params[i].value;
ret = drmIoctl(virtgpu_, DRM_IOCTL_VIRTGPU_GETPARAM, &get_param);
if (ret < 0) {
fprintf(stderr, "DRM_IOCTL_VIRTGPU_GET_PARAM failed with %s\n",
strerror(errno));
close(virtgpu_);
virtgpu_ = -1;
return -EINVAL;
}
if (params[i].param == VIRTGPU_PARAM_SUPPORTED_CAPSET_IDs) {
if ((params[i].value & (1 << CAPSET_CROSS_DOMAIN)) == 0)
return -ENOTSUP;
}
}
args.cap_set_id = CAPSET_CROSS_DOMAIN;
args.size = sizeof(struct CrossDomainCapabilities);
args.addr = (unsigned long long)&cross_domain_caps;
ret = drmIoctl(virtgpu_, DRM_IOCTL_VIRTGPU_GET_CAPS, &args);
if (ret) {
fprintf(stderr, "DRM_IOCTL_VIRTGPU_GET_CAPS failed with %s\n",
strerror(errno));
return ret;
}
if (cross_domain_caps.supports_dmabuf)
supports_dmabuf_ = true;
supports_wayland = cross_domain_caps.supported_channels &
(1 << CROSS_DOMAIN_CHANNEL_TYPE_WAYLAND);
if (!supports_wayland) {
fprintf(stderr, "Wayland support not present on host.\n");
return -ENOTSUP;
}
return 0;
}
bool VirtGpuChannel::supports_dmabuf(void) {
return supports_dmabuf_;
}
int32_t VirtGpuChannel::create_context(int& out_channel_fd) {
int ret;
struct drm_virtgpu_map map = {0};
struct drm_virtgpu_context_init init = {0};
struct drm_virtgpu_resource_create_blob drm_rc_blob = {0};
struct drm_virtgpu_context_set_param ctx_set_params[3] = {{0}};
struct CrossDomainInit cmd_init = {{0}};
// Initialize the cross domain context. Create one fence context to wait for
// metadata queries.
ctx_set_params[0].param = VIRTGPU_CONTEXT_PARAM_CAPSET_ID;
ctx_set_params[0].value = CAPSET_CROSS_DOMAIN;
ctx_set_params[1].param = VIRTGPU_CONTEXT_PARAM_NUM_RINGS;
ctx_set_params[1].value = 2;
ctx_set_params[2].param = VIRTGPU_CONTEXT_PARAM_POLL_RINGS_MASK;
ctx_set_params[2].value = 1 << CROSS_DOMAIN_CHANNEL_RING;
init.ctx_set_params = (unsigned long long)&ctx_set_params[0];
init.num_params = 3;
ret = drmIoctl(virtgpu_, DRM_IOCTL_VIRTGPU_CONTEXT_INIT, &init);
if (ret) {
fprintf(stderr, "DRM_IOCTL_VIRTGPU_CONTEXT_INIT failed with %s\n",
strerror(errno));
return ret;
}
// Create a shared ring buffer to read metadata queries.
drm_rc_blob.size = PAGE_SIZE;
drm_rc_blob.blob_mem = VIRTGPU_BLOB_MEM_GUEST;
drm_rc_blob.blob_flags = VIRTGPU_BLOB_FLAG_USE_MAPPABLE;
ret =
drmIoctl(virtgpu_, DRM_IOCTL_VIRTGPU_RESOURCE_CREATE_BLOB, &drm_rc_blob);
if (ret < 0) {
fprintf(stderr, "DRM_VIRTGPU_RESOURCE_CREATE_BLOB failed with %s\n",
strerror(errno));
return ret;
}
ring_handle_ = drm_rc_blob.bo_handle;
// Map shared ring buffer.
map.handle = ring_handle_;
ret = drmIoctl(virtgpu_, DRM_IOCTL_VIRTGPU_MAP, &map);
if (ret < 0) {
fprintf(stderr, "DRM_IOCTL_VIRTGPU_MAP failed with %s\n", strerror(errno));
return ret;
}
ring_addr_ = mmap(0, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, virtgpu_,
map.offset);
if (ring_addr_ == MAP_FAILED) {
fprintf(stderr, "mmap failed with %s\n", strerror(errno));
return ret;
}
// Notify host about ring buffer
cmd_init.hdr.cmd = CROSS_DOMAIN_CMD_INIT;
cmd_init.hdr.cmd_size = sizeof(struct CrossDomainInit);
cmd_init.ring_id = drm_rc_blob.res_handle;
cmd_init.channel_type = CROSS_DOMAIN_CHANNEL_TYPE_WAYLAND;
ret = submit_cmd((uint32_t*)&cmd_init, cmd_init.hdr.cmd_size,
CROSS_DOMAIN_RING_NONE, false);
if (ret < 0)
return ret;
// Start polling right after initialization
ret = channel_poll();
if (ret < 0)
return ret;
out_channel_fd = virtgpu_;
return 0;
}
int32_t VirtGpuChannel::create_pipe(int& out_pipe_fd) {
// This may be undesirable given your point of view, since the host
// generates the descriptor IDs. However, given the way Sommelier is
// designed and the order of events that occurs, this is safe to do.
// The host will verify this assumption, and we can always change it
// later. But this avoids waiting for the host to create a pipe and
// return the ID.
descriptor_id_ += 2;
return create_pipe_internal(out_pipe_fd, descriptor_id_,
CROSS_DOMAIN_ID_TYPE_READ_PIPE);
}
int32_t VirtGpuChannel::send(const struct WaylandSendReceive& send) {
int32_t ret;
uint8_t cmd_buffer[DEFAULT_BUFFER_SIZE];
struct CrossDomainSendReceive* cmd_send =
(struct CrossDomainSendReceive*)cmd_buffer;
void* send_data = &cmd_buffer[sizeof(struct CrossDomainSendReceive)];
memset(cmd_send, 0, sizeof(struct CrossDomainSendReceive));
if (send.data_size > max_send_size())
return -EINVAL;
if (send.num_fds > CROSS_DOMAIN_MAX_IDENTIFIERS)
return -EINVAL;
cmd_send->hdr.cmd = CROSS_DOMAIN_CMD_SEND;
cmd_send->hdr.cmd_size =
sizeof(struct CrossDomainSendReceive) + send.data_size;
memcpy(send_data, send.data, send.data_size);
cmd_send->opaque_data_size = send.data_size;
for (uint32_t i = 0; i < CROSS_DOMAIN_MAX_IDENTIFIERS; i++) {
if (i >= send.num_fds)
break;
ret = fd_analysis(send.fds[i], cmd_send->identifiers[i],
cmd_send->identifier_types[i]);
if (ret)
return ret;
cmd_send->num_identifiers++;
}
ret = submit_cmd((uint32_t*)cmd_send, cmd_send->hdr.cmd_size,
CROSS_DOMAIN_RING_NONE, false);
if (ret < 0)
return ret;
return 0;
}
int32_t VirtGpuChannel::handle_channel_event(
enum WaylandChannelEvent& event_type,
struct WaylandSendReceive& receive,
int& out_read_pipe) {
int32_t ret;
struct CrossDomainHeader* cmd_hdr = (struct CrossDomainHeader*)ring_addr_;
ssize_t bytes_read;
struct drm_event dummy_event;
bytes_read = read(virtgpu_, &dummy_event, sizeof(struct drm_event));
if (bytes_read < (int)sizeof(struct drm_event)) {
fprintf(stderr, "invalid event size\n");
return -EINVAL;
}
if (dummy_event.type != VIRTGPU_EVENT_FENCE_SIGNALED) {
fprintf(stderr, "invalid event type\n");
return -EINVAL;
}
if (cmd_hdr->cmd == CROSS_DOMAIN_CMD_RECEIVE) {
event_type = WaylandChannelEvent::Receive;
ret = handle_receive(event_type, receive, out_read_pipe);
if (ret)
return ret;
} else if (cmd_hdr->cmd == CROSS_DOMAIN_CMD_READ) {
event_type = WaylandChannelEvent::Read;
ret = handle_read();
if (ret)
return ret;
} else {
return -EINVAL;
}
// Start polling again
ret = channel_poll();
if (ret < 0)
return ret;
return 0;
}
int32_t VirtGpuChannel::allocate(
const struct WaylandBufferCreateInfo& create_info,
struct WaylandBufferCreateOutput& create_output) {
int32_t ret;
uint64_t blob_id;
ret = image_query(create_info, create_output, blob_id);
if (ret < 0) {
fprintf(stderr, "image query failed\n");
return ret;
}
return create_host_blob(blob_id, create_output.host_size, create_output.fd);
}
int32_t VirtGpuChannel::sync(int dmabuf_fd, uint64_t flags) {
// Unimplemented for now, but just need CROSS_DOMAIN_CMD_SYNC.
return 0;
}
int32_t VirtGpuChannel::handle_pipe(int read_fd, bool readable, bool& hang_up) {
uint8_t cmd_buffer[DEFAULT_BUFFER_SIZE];
ssize_t bytes_read;
int ret;
size_t index;
struct CrossDomainReadWrite* cmd_write =
(struct CrossDomainReadWrite*)cmd_buffer;
void* write_data = &cmd_buffer[sizeof(struct CrossDomainReadWrite)];
memset(cmd_write, 0, sizeof(struct CrossDomainReadWrite));
cmd_write->hdr.cmd = CROSS_DOMAIN_CMD_WRITE;
cmd_write->identifier = 0xffffffff;
ret = pipe_lookup(CROSS_DOMAIN_ID_TYPE_WRITE_PIPE, cmd_write->identifier,
read_fd, index);
if (ret < 0)
return -EINVAL;
if (readable) {
bytes_read = read(read_fd, write_data, MAX_WRITE_SIZE);
if (bytes_read > 0) {
cmd_write->opaque_data_size = bytes_read;
if ((size_t)bytes_read < MAX_WRITE_SIZE)
hang_up = true;
else
hang_up = false;
} else if (bytes_read == 0) {
hang_up = true;
} else {
return -EINVAL;
}
}
cmd_write->hdr.cmd_size =
sizeof(struct CrossDomainReadWrite) + cmd_write->opaque_data_size;
cmd_write->hang_up = hang_up;
ret = submit_cmd((uint32_t*)cmd_write, cmd_write->hdr.cmd_size,
CROSS_DOMAIN_RING_NONE, false);
if (ret < 0)
return ret;
if (hang_up) {
close(read_fd);
std::swap(pipe_cache_[index], pipe_cache_.back());
pipe_cache_.pop_back();
}
return 0;
}
int32_t VirtGpuChannel::submit_cmd(uint32_t* cmd,
uint32_t size,
uint32_t ring_idx,
bool wait) {
int32_t ret;
struct drm_virtgpu_3d_wait wait_3d = {0};
struct drm_virtgpu_execbuffer exec = {0};
exec.command = (uint64_t)&cmd[0];
exec.size = size;
if (ring_idx != CROSS_DOMAIN_RING_NONE) {
exec.flags = VIRTGPU_EXECBUF_RING_IDX;
exec.ring_idx = ring_idx;
exec.bo_handles = (uint64_t)&ring_handle_;
exec.num_bo_handles = 1;
}
ret = drmIoctl(virtgpu_, DRM_IOCTL_VIRTGPU_EXECBUFFER, &exec);
if (ret < 0) {
fprintf(stderr, "DRM_IOCTL_VIRTGPU_EXECBUFFER failed with %s\n",
strerror(errno));
return -EINVAL;
}
// This is the most traditional way to wait for virtgpu to be finished. We
// submit a list of handles to the GPU, and wait for the GPU to be done
// processing them. In our case, the handle is the shared ring buffer between
// the guest proxy (Sommelier) and host compositor proxy (cross domain context
// type in crosvm). More sophistication will be needed in the future if the
// virtgpu approach has any hope of success.
if (wait) {
ret = -EAGAIN;
while (ret == -EAGAIN) {
wait_3d.handle = ring_handle_;
ret = drmIoctl(virtgpu_, DRM_IOCTL_VIRTGPU_WAIT, &wait_3d);
}
}
if (ret < 0) {
fprintf(stderr, "DRM_IOCTL_VIRTGPU_WAIT failed with %s\n", strerror(errno));
return ret;
}
return 0;
}
int32_t VirtGpuChannel::image_query(const struct WaylandBufferCreateInfo& input,
struct WaylandBufferCreateOutput& output,
uint64_t& blob_id) {
int32_t ret = 0;
uint32_t* addr = (uint32_t*)ring_addr_;
struct CrossDomainGetImageRequirements cmd_get_reqs = {{0}};
struct BufferDescription new_desc = {{0}};
// Sommelier is single threaded, so no need for locking.
for (const auto& desc : description_cache_) {
if (desc.input.width == input.width && desc.input.height == input.height &&
desc.input.drm_format == input.drm_format) {
memcpy(&output, &desc.output, sizeof(struct WaylandBufferCreateOutput));
blob_id = (uint64_t)desc.blob_id;
return 0;
}
}
cmd_get_reqs.hdr.cmd = CROSS_DOMAIN_CMD_GET_IMAGE_REQUIREMENTS;
cmd_get_reqs.hdr.cmd_size = sizeof(struct CrossDomainGetImageRequirements);
cmd_get_reqs.width = input.width;
cmd_get_reqs.height = input.height;
cmd_get_reqs.drm_format = input.drm_format;
// Assumes a gbm-like API on the host
cmd_get_reqs.flags = GBM_BO_USE_LINEAR | GBM_BO_USE_SCANOUT;
ret = submit_cmd((uint32_t*)&cmd_get_reqs, cmd_get_reqs.hdr.cmd_size,
CROSS_DOMAIN_QUERY_RING, true);
if (ret < 0)
return ret;
new_desc.output.fd = -1;
memcpy(&new_desc.input, &input, sizeof(struct WaylandBufferCreateInfo));
memcpy(&new_desc.output.strides, &addr[0], 4 * sizeof(uint32_t));
memcpy(&new_desc.output.offsets, &addr[4], 4 * sizeof(uint32_t));
memcpy(&new_desc.output.host_size, &addr[10], sizeof(uint64_t));
memcpy(&new_desc.blob_id, &addr[12], sizeof(uint32_t));
// Sanity check
if (!input.dmabuf) {
if (new_desc.output.host_size < input.size) {
fprintf(stderr, "invalid host size\n");
return -EINVAL;
}
}
memcpy(&output.strides, &new_desc.output.strides, 4 * sizeof(uint32_t));
memcpy(&output.offsets, &new_desc.output.offsets, 4 * sizeof(uint32_t));
output.host_size = new_desc.output.host_size;
blob_id = (uint64_t)new_desc.blob_id;
description_cache_.push_back(new_desc);
return 0;
}
int32_t VirtGpuChannel::close_gem_handle(uint32_t gem_handle) {
int32_t ret;
struct drm_gem_close gem_close = {0};
gem_close.handle = gem_handle;
ret = drmIoctl(virtgpu_, DRM_IOCTL_GEM_CLOSE, &gem_close);
if (ret) {
fprintf(stderr, "DRM_IOCTL_GEM_CLOSE failed (handle=%x) error %s\n",
gem_handle, strerror(errno));
return -errno;
}
return 0;
}
int32_t VirtGpuChannel::channel_poll(void) {
int32_t ret;
struct CrossDomainPoll cmd_poll = {{0}};
cmd_poll.hdr.cmd = CROSS_DOMAIN_CMD_POLL;
cmd_poll.hdr.cmd_size = sizeof(struct CrossDomainPoll);
ret = submit_cmd((uint32_t*)&cmd_poll, cmd_poll.hdr.cmd_size,
CROSS_DOMAIN_CHANNEL_RING, false);
if (ret < 0)
return ret;
return 0;
}
int32_t VirtGpuChannel::create_host_blob(uint64_t blob_id,
uint64_t size,
int& out_fd) {
int32_t ret;
struct drm_virtgpu_resource_create_blob drm_rc_blob = {0};
drm_rc_blob.size = size;
drm_rc_blob.blob_mem = VIRTGPU_BLOB_MEM_HOST3D;
drm_rc_blob.blob_flags =
VIRTGPU_BLOB_FLAG_USE_MAPPABLE | VIRTGPU_BLOB_FLAG_USE_SHAREABLE;
drm_rc_blob.blob_id = blob_id;
ret =
drmIoctl(virtgpu_, DRM_IOCTL_VIRTGPU_RESOURCE_CREATE_BLOB, &drm_rc_blob);
if (ret < 0) {
fprintf(stderr, "DRM_VIRTGPU_RESOURCE_CREATE_BLOB failed with %s\n",
strerror(errno));
return -errno;
}
ret = drmPrimeHandleToFD(virtgpu_, drm_rc_blob.bo_handle,
DRM_CLOEXEC | DRM_RDWR, &out_fd);
if (ret < 0) {
fprintf(stderr, "drmPrimeHandleToFD failed with with %s\n",
strerror(errno));
return -errno;
}
// dma-buf owns the reference to underlying memory now.
ret = close_gem_handle(drm_rc_blob.bo_handle);
if (ret < 0)
return ret;
return 0;
}
int32_t VirtGpuChannel::create_fd(uint32_t identifier,
uint32_t identifier_type,
uint32_t identifier_size,
int& out_fd) {
// Update descriptor ID based on latest host information.
descriptor_id_ = identifier;
if (identifier_type == CROSS_DOMAIN_ID_TYPE_VIRTGPU_BLOB) {
return create_host_blob((uint64_t)identifier, (uint64_t)identifier_size,
out_fd);
} else if (identifier_type == CROSS_DOMAIN_ID_TYPE_WRITE_PIPE) {
return create_pipe_internal(out_fd, identifier, identifier_type);
} else {
return -EINVAL;
}
}
int32_t VirtGpuChannel::fd_analysis(int fd,
uint32_t& identifier,
uint32_t& identifier_type) {
int32_t ret = 0;
uint32_t gem_handle;
ret = drmPrimeFDToHandle(virtgpu_, fd, &gem_handle);
if (ret == 0) {
struct drm_virtgpu_resource_info drm_res_info = {0};
drm_res_info.bo_handle = gem_handle;
ret = drmIoctl(virtgpu_, DRM_IOCTL_VIRTGPU_RESOURCE_INFO, &drm_res_info);
if (ret) {
fprintf(stderr, "resource info failed\n");
return ret;
}
identifier = drm_res_info.res_handle;
identifier_type = CROSS_DOMAIN_ID_TYPE_VIRTGPU_BLOB;
} else {
// If it's not a blob, the only other option is a pipe. Check to confirm.
uint32_t inode;
ret = fstat_pipe(fd, inode);
if (ret)
return ret;
for (const auto& pipe_desc : pipe_cache_) {
if (pipe_desc.inode == inode) {
identifier = pipe_desc.identifier;
identifier_type = pipe_desc.identifier_type;
return 0;
}
}
return -EINVAL;
}
return 0;
}
int32_t VirtGpuChannel::create_pipe_internal(int& out_pipe_fd,
uint32_t identifier,
uint32_t identifier_type) {
int32_t ret;
int fds[2];
struct PipeDescription pipe_desc = {0};
bool return_read_pipe = false;
// When proxying a Wayland pipe, we return one end to the WaylandChannel
// consumer and keep one end to ourselves. Keeping both ends isn't useful.
if (identifier_type == CROSS_DOMAIN_ID_TYPE_READ_PIPE)
return_read_pipe = true;
else if (identifier_type == CROSS_DOMAIN_ID_TYPE_WRITE_PIPE)
return_read_pipe = false;
else
return -EINVAL;
ret = pipe(fds);
if (ret < 0) {
fprintf(stderr, "pipe creation failed with %s\n", strerror(errno));
return -errno;
}
// The same inode number is used for the read/write ends of the pipe.
ret = fstat_pipe(fds[0], pipe_desc.inode);
if (ret < 0)
return ret;
pipe_desc.read_fd = fds[0];
pipe_desc.write_fd = fds[1];
pipe_desc.identifier = identifier;
pipe_desc.identifier_type = identifier_type;
pipe_cache_.push_back(pipe_desc);
if (return_read_pipe)
out_pipe_fd = fds[0];
else
out_pipe_fd = fds[1];
return 0;
}
int32_t VirtGpuChannel::handle_receive(enum WaylandChannelEvent& event_type,
struct WaylandSendReceive& receive,
int& out_read_pipe) {
int ret;
struct CrossDomainSendReceive* cmd_receive =
(struct CrossDomainSendReceive*)ring_addr_;
uint8_t* recv_data =
(uint8_t*)ring_addr_ + sizeof(struct CrossDomainSendReceive);
for (uint32_t i = 0; i < CROSS_DOMAIN_MAX_IDENTIFIERS; i++) {
if (i < cmd_receive->num_identifiers) {
ret = create_fd(cmd_receive->identifiers[i],
cmd_receive->identifier_types[i],
cmd_receive->identifier_sizes[i], receive.fds[i]);
if (ret)
return ret;
receive.num_fds++;
if (cmd_receive->identifier_types[i] == CROSS_DOMAIN_ID_TYPE_WRITE_PIPE) {
size_t index;
int ret = 0;
if (out_read_pipe >= 0)
return -EINVAL;
ret = pipe_lookup(cmd_receive->identifier_types[i],
cmd_receive->identifiers[i], out_read_pipe, index);
if (ret < 0)
return -EINVAL;
event_type = WaylandChannelEvent::ReceiveAndProxy;
}
} else {
break;
}
}
if (cmd_receive->opaque_data_size > 0) {
receive.data =
reinterpret_cast<uint8_t*>(calloc(1, cmd_receive->opaque_data_size));
if (!receive.data)
return -ENOMEM;
memcpy(receive.data, recv_data, cmd_receive->opaque_data_size);
}
receive.data_size = cmd_receive->opaque_data_size;
return 0;
}
int32_t VirtGpuChannel::handle_read() {
int write_fd = -1;
int ret = 0;
ssize_t bytes_written;
size_t index;
struct CrossDomainReadWrite* cmd_read =
(struct CrossDomainReadWrite*)ring_addr_;
uint8_t* read_data =
(uint8_t*)ring_addr_ + sizeof(struct CrossDomainReadWrite);
ret = pipe_lookup(CROSS_DOMAIN_ID_TYPE_READ_PIPE, cmd_read->identifier,
write_fd, index);
if (ret < 0)
return -EINVAL;
bytes_written = write(write_fd, read_data, cmd_read->opaque_data_size);
if (bytes_written < (ssize_t)cmd_read->opaque_data_size) {
fprintf(stderr, "failed to write all necessary bytes\n");
return -EINVAL;
}
if (cmd_read->hang_up) {
close(write_fd);
std::swap(pipe_cache_[index], pipe_cache_.back());
pipe_cache_.pop_back();
}
return 0;
}
int32_t VirtGpuChannel::pipe_lookup(uint32_t identifier_type,
uint32_t& identifier,
int& fd,
size_t& index) {
index = 0;
for (const auto& pipe_desc : pipe_cache_) {
if (pipe_desc.identifier == identifier) {
// The host and guest are proxying the read operation, need to write to
// internally owned file descriptor.
if (identifier_type == CROSS_DOMAIN_ID_TYPE_READ_PIPE) {
fd = pipe_desc.write_fd;
return 0;
}
// The host and guest are proxying the write operation, need to read from
// internally owned file descriptor.
if (identifier_type == CROSS_DOMAIN_ID_TYPE_WRITE_PIPE) {
fd = pipe_desc.read_fd;
return 0;
}
}
if (fd == pipe_desc.read_fd || fd == pipe_desc.write_fd) {
identifier = pipe_desc.identifier;
return 0;
}
index++;
}
return -EINVAL;
}
size_t VirtGpuChannel::max_send_size(void) {
return MAX_SEND_SIZE;
}

View File

@ -0,0 +1,111 @@
// Copyright 2021 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef VM_TOOLS_SOMMELIER_VIRTUALIZATION_VIRTGPU_CROSS_DOMAIN_PROTOCOL_H_
#define VM_TOOLS_SOMMELIER_VIRTUALIZATION_VIRTGPU_CROSS_DOMAIN_PROTOCOL_H_
#include <stdint.h>
// Cross-domain commands (only a maximum of 255 supported)
#define CROSS_DOMAIN_CMD_INIT 1
#define CROSS_DOMAIN_CMD_GET_IMAGE_REQUIREMENTS 2
#define CROSS_DOMAIN_CMD_POLL 3
#define CROSS_DOMAIN_CMD_SEND 4
#define CROSS_DOMAIN_CMD_RECEIVE 5
#define CROSS_DOMAIN_CMD_READ 6
#define CROSS_DOMAIN_CMD_WRITE 7
// Channel types (must match rutabaga channel types)
#define CROSS_DOMAIN_CHANNEL_TYPE_WAYLAND 0x0001
#define CROSS_DOMAIN_CHANNEL_TYPE_CAMERA 0x0002
// The maximum number of identifiers (value based on wp_linux_dmabuf)
#define CROSS_DOMAIN_MAX_IDENTIFIERS 4
// virtgpu memory resource ID. Also works with non-blob memory resources,
// despite the name.
#define CROSS_DOMAIN_ID_TYPE_VIRTGPU_BLOB 1
// virtgpu synchronization resource id.
#define CROSS_DOMAIN_ID_TYPE_VIRTGPU_SYNC 2
// ID for Wayland pipe used for reading. The reading is done by the guest proxy
// and the host proxy. The host sends the write end of the proxied pipe over
// the host Wayland socket.
#define CROSS_DOMAIN_ID_TYPE_READ_PIPE 3
// ID for Wayland pipe used for writing. The writing is done by the guest and
// the host proxy. The host receives the write end of the pipe over the host
// Wayland socket.
#define CROSS_DOMAIN_ID_TYPE_WRITE_PIPE 4
// No ring used
#define CROSS_DOMAIN_RING_NONE 0xffffffff
// A ring for metadata queries.
#define CROSS_DOMAIN_QUERY_RING 0
// A ring based on this particular context's channel.
#define CROSS_DOMAIN_CHANNEL_RING 1
struct CrossDomainCapabilities {
uint32_t version;
uint32_t supported_channels;
uint32_t supports_dmabuf;
uint32_t supports_external_gpu_memory;
};
struct CrossDomainImageRequirements {
uint32_t strides[4];
uint32_t offsets[4];
uint64_t modifier;
uint64_t size;
uint32_t blob_id;
uint32_t map_info;
int32_t memory_idx;
int32_t physical_device_idx;
};
struct CrossDomainHeader {
uint8_t cmd;
uint8_t fence_ctx_idx;
uint16_t cmd_size;
uint32_t pad;
};
struct CrossDomainInit {
struct CrossDomainHeader hdr;
uint32_t ring_id;
uint32_t channel_type;
};
struct CrossDomainGetImageRequirements {
struct CrossDomainHeader hdr;
uint32_t width;
uint32_t height;
uint32_t drm_format;
uint32_t flags;
};
struct CrossDomainPoll {
struct CrossDomainHeader hdr;
uint64_t pad;
};
struct CrossDomainSendReceive {
struct CrossDomainHeader hdr;
uint32_t num_identifiers;
uint32_t opaque_data_size;
uint32_t identifiers[CROSS_DOMAIN_MAX_IDENTIFIERS];
uint32_t identifier_types[CROSS_DOMAIN_MAX_IDENTIFIERS];
uint32_t identifier_sizes[CROSS_DOMAIN_MAX_IDENTIFIERS];
};
struct CrossDomainReadWrite {
struct CrossDomainHeader hdr;
uint32_t identifier;
uint32_t hang_up;
uint32_t opaque_data_size;
uint32_t pad;
};
#endif

View File

@ -0,0 +1,225 @@
// Copyright 2020 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <errno.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include "linux-headers/virtwl.h" // NOLINT(build/include_directory)
#include "wayland_channel.h" // NOLINT(build/include_directory)
#define VIRTWL_DEVICE "/dev/wl0"
#define MAX_SEND_SIZE (DEFAULT_BUFFER_SIZE - sizeof(struct virtwl_ioctl_txn))
VirtWaylandChannel::~VirtWaylandChannel() {
if (virtwl_ >= 0)
close(virtwl_);
}
int32_t VirtWaylandChannel::init() {
virtwl_ = open(VIRTWL_DEVICE, O_RDWR);
int32_t ret;
struct WaylandBufferCreateInfo create_info = {0};
struct WaylandBufferCreateOutput create_output = {0};
create_output.fd = -1;
if (virtwl_ == -1)
return -errno;
create_info.dmabuf = true;
supports_dmabuf_ = true;
ret = allocate(create_info, create_output);
if (ret && errno == ENOTTY) {
fprintf(stderr,
"warning: virtwl-dmabuf driver not supported by host,"
" using virtwl instead\n");
supports_dmabuf_ = false;
} else if (create_output.fd >= 0) {
// Close the returned dmabuf fd in case the invalid dmabuf metadata
// given above actually manages to return an fd successfully.
close(create_output.fd);
create_output.fd = -1;
}
return 0;
}
bool VirtWaylandChannel::supports_dmabuf(void) {
return supports_dmabuf_;
}
int32_t VirtWaylandChannel::create_context(int& out_channel_fd) {
int ret;
struct virtwl_ioctl_new new_ctx = {
.type = VIRTWL_IOCTL_NEW_CTX,
.fd = -1,
.flags = 0,
};
ret = ioctl(virtwl_, VIRTWL_IOCTL_NEW, &new_ctx);
if (ret)
return -errno;
out_channel_fd = new_ctx.fd;
return 0;
}
int32_t VirtWaylandChannel::create_pipe(int& out_pipe_fd) {
int ret;
struct virtwl_ioctl_new new_pipe = {
.type = VIRTWL_IOCTL_NEW_PIPE_READ,
.fd = -1,
.flags = 0,
};
new_pipe.size = 0;
ret = ioctl(virtwl_, VIRTWL_IOCTL_NEW, &new_pipe);
if (ret)
return -errno;
out_pipe_fd = new_pipe.fd;
return 0;
}
int32_t VirtWaylandChannel::send(const struct WaylandSendReceive& send) {
int ret;
uint8_t ioctl_buffer[DEFAULT_BUFFER_SIZE];
struct virtwl_ioctl_txn* txn = (struct virtwl_ioctl_txn*)ioctl_buffer;
void* send_data = &txn->data;
if (send.data_size > max_send_size())
return -EINVAL;
memcpy(send_data, send.data, send.data_size);
for (uint32_t i = 0; i < WAYLAND_MAX_FDs; i++) {
if (i < send.num_fds) {
txn->fds[i] = send.fds[i];
} else {
txn->fds[i] = -1;
}
}
txn->len = send.data_size;
ret = ioctl(send.channel_fd, VIRTWL_IOCTL_SEND, txn);
if (ret)
return -errno;
return 0;
}
int32_t VirtWaylandChannel::handle_channel_event(
enum WaylandChannelEvent& event_type,
struct WaylandSendReceive& receive,
int& out_read_pipe) {
int ret;
uint8_t ioctl_buffer[DEFAULT_BUFFER_SIZE];
struct virtwl_ioctl_txn* txn = (struct virtwl_ioctl_txn*)ioctl_buffer;
size_t max_recv_size = sizeof(ioctl_buffer) - sizeof(struct virtwl_ioctl_txn);
void* recv_data = &txn->data;
txn->len = max_recv_size;
ret = ioctl(receive.channel_fd, VIRTWL_IOCTL_RECV, txn);
if (ret)
return -errno;
for (uint32_t i = 0; i < WAYLAND_MAX_FDs; i++) {
if (txn->fds[i] >= 0) {
receive.num_fds++;
receive.fds[i] = txn->fds[i];
} else {
break;
}
}
if (txn->len > 0) {
receive.data = reinterpret_cast<uint8_t*>(calloc(1, txn->len));
if (!receive.data)
return -ENOMEM;
memcpy(receive.data, recv_data, txn->len);
}
receive.data_size = txn->len;
event_type = WaylandChannelEvent::Receive;
return 0;
}
int32_t VirtWaylandChannel::allocate(
const struct WaylandBufferCreateInfo& create_info,
struct WaylandBufferCreateOutput& create_output) {
int ret;
struct virtwl_ioctl_new ioctl_new = {0};
if (create_info.dmabuf) {
ioctl_new.type = VIRTWL_IOCTL_NEW_DMABUF;
ioctl_new.fd = -1;
ioctl_new.flags = 0;
ioctl_new.dmabuf.width = create_info.width;
ioctl_new.dmabuf.height = create_info.height;
ioctl_new.dmabuf.format = create_info.drm_format;
} else {
ioctl_new.type = VIRTWL_IOCTL_NEW_ALLOC;
ioctl_new.fd = -1;
ioctl_new.flags = 0;
ioctl_new.size = create_info.size;
create_output.host_size = create_info.size;
}
ret = ioctl(virtwl_, VIRTWL_IOCTL_NEW, &ioctl_new);
if (ret)
return -errno;
if (create_info.dmabuf) {
create_output.strides[0] = ioctl_new.dmabuf.stride0;
create_output.strides[1] = ioctl_new.dmabuf.stride1;
create_output.strides[2] = ioctl_new.dmabuf.stride2;
create_output.offsets[0] = ioctl_new.dmabuf.offset0;
create_output.offsets[1] = ioctl_new.dmabuf.offset1;
create_output.offsets[2] = ioctl_new.dmabuf.offset2;
// The common layer will consider multi-planar sizes as needed.
create_output.host_size = create_output.strides[0] * create_info.height;
}
create_output.fd = ioctl_new.fd;
return 0;
}
int32_t VirtWaylandChannel::sync(int dmabuf_fd, uint64_t flags) {
struct virtwl_ioctl_dmabuf_sync sync = {0};
int ret;
sync.flags = flags;
ret = ioctl(dmabuf_fd, VIRTWL_IOCTL_DMABUF_SYNC, &sync);
if (ret)
return -errno;
return 0;
}
int32_t VirtWaylandChannel::handle_pipe(int read_fd,
bool readable,
bool& hang_up) {
return 0;
}
size_t VirtWaylandChannel::max_send_size(void) {
return MAX_SEND_SIZE;
}

View File

@ -0,0 +1,288 @@
// Copyright 2020 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef VM_TOOLS_SOMMELIER_VIRTUALIZATION_WAYLAND_CHANNEL_H_
#define VM_TOOLS_SOMMELIER_VIRTUALIZATION_WAYLAND_CHANNEL_H_
#include <cstdint>
#include <sys/mman.h>
#include <vector>
/*
* Copied from `VIRTWL_SEND_MAX_ALLOCS`. It was originally set this way
* because it seemed like a reasonable limit.
*/
#define WAYLAND_MAX_FDs 28
// Default buffer size based on the size of a typical page.
#define DEFAULT_BUFFER_SIZE 4096
struct WaylandSendReceive {
int channel_fd;
int fds[WAYLAND_MAX_FDs];
uint32_t num_fds;
uint8_t* data;
size_t data_size;
};
enum WaylandChannelEvent {
None,
Receive,
ReceiveAndProxy,
Read,
};
struct WaylandBufferCreateInfo {
/*
* If true, create a dmabuf on the host. If not, create a shared memory
* region. A dmabuf can be scanned out by the display engine directly,
* enabling zero copy. A shared memory region necessitates a copy to a
* dma-buf by the host compositor.
*/
bool dmabuf;
/*
* dma-buf parameters. The allocation is done by host minigbm and used when
* crosvm is built with the "wl-dmabuf" feature and virtgpu 3d is not
* enabled. The modifier is not present, because we only want to allocate
* linear zero-copy buffers in this case. The modifier makes sense when
* virtgpu 3d is enabled, but in that case guest Mesa gbm (backed by Virgl)
* allocates the resource, not sommelier.
*/
uint32_t width;
uint32_t height;
uint32_t drm_format;
/*
* Shared memory region parameters. The allocation is done by memfd(..) on
* the host.
*/
uint32_t size;
};
/*
* Linux mode-setting APIs [drmModeAddFB2(..)] and Wayland normally specify
* four planes, even though three are used in practice. Follow that convention
* here.
*/
struct WaylandBufferCreateOutput {
int fd;
uint32_t offsets[4];
uint32_t strides[4];
uint64_t host_size;
};
class WaylandChannel {
public:
WaylandChannel() {}
virtual ~WaylandChannel() {}
// Initializes the Wayland Channel. Returns 0 on success, -errno on failure.
virtual int32_t init() = 0;
// Returns true if the Wayland channel supports dmabuf, false otherwise. If
// dmabuf is supported, Sommelier will use the `zwp_linux_dmabuf_v1`
// protocol.
virtual bool supports_dmabuf(void) = 0;
// Creates a new context for handling the wayland command stream. Returns 0
// on success, and a pollable `out_channel_fd`. This fd represents the
// connection to the host compositor, and used for subsequent `send` and
// `receive` operations.
//
// Returns -errno on failure.
virtual int32_t create_context(int& out_channel_fd) = 0;
// Creates a new clipboard pipe for Wayland input. Note this interface can't
// wrap a call to "pipe", and is named based on VIRTWL_IOCTL_NEW_PIPE. A new
// interface may be designed in the future.
//
// Returns 0 on success, and a readable `out_pipe_fd`.
// Returns -errno on failure.
virtual int32_t create_pipe(int& out_pipe_fd) = 0;
// Sends fds and associated commands to the host [like sendmsg(..)]. The fds
// are converted to host handles using an implementation specific method.
// For virtwl, either:
// (a) virtwl allocated resources are sent.
// (b) The virtgpu resource handle is fished out by virtwl.
//
// Returns 0 on success. Returns -errno on failure. If `send.data_size` is
// than greater zero, then the caller must provide a pointer to valid memory
// in `send.data`.
virtual int32_t send(const struct WaylandSendReceive& send) = 0;
// Handles a poll event on the channel file descriptor.
//
// Returns 0 on success. Returns -errno on failure. On success, the type of
// event is given by `event_type`.
//
// If `event_type` is WaylandChannelEvent::Receive, the caller must forward
// received fds and associated commands to the client.
//
// If `event_type` is WaylandChannelEvent::ReceiveAndProxy, `out_read_pipe`
// is also returned in addition to the `receive` data. The caller does not
// take ownership of `out_read_pipe`. The caller must poll `out_read_pipe`
// in addition to forwarding the data given by `receive`. The `handle_pipe`
// function must be called the case of `out_read_pipe` event.
//
// In both above cases, if the returned `receive.data_size` is than greater
// zero, then the caller takes ownership of `receive.data` and must free(..)
// the memory when appropriate.
//
// If `event_type` is WaylandChannelEvent::Read, then both `out_read_pipe` and
// `receive` are meaningless. The implementation handles the event internally.
virtual int32_t handle_channel_event(enum WaylandChannelEvent& event_type,
struct WaylandSendReceive& receive,
int& out_read_pipe) = 0;
// Allocates a shared memory resource or dma-buf on the host. Maps it into
// the guest. The intended use case for this function is sharing resources
// with the host compositor when virtgpu 3d is not enabled.
//
// Returns 0 on success. Returns -errno on success.
virtual int32_t allocate(const struct WaylandBufferCreateInfo& create_info,
struct WaylandBufferCreateOutput& create_output) = 0;
// Synchronizes accesses to previously created host dma-buf.
// Returns 0 on success. Returns -errno on failure.
virtual int32_t sync(int dmabuf_fd, uint64_t flags) = 0;
// Reads from the specified `read_fd` and forwards to the host if `readable`
// is true. Closes the `read_fd` and the proxied write fd on the host if
// `hang_up` is true and all the data has been read.
//
// `read_fd` *must* be a read pipe given by `handle_channel_event` when the
// `event_type` is WaylandChannelEvent::ReceiveAndProxy.
virtual int32_t handle_pipe(int read_fd, bool readable, bool& hang_up) = 0;
// Returns the maximum size of opaque data that the channel is able to handle
// in the `send` function. Must be less than or equal to DEFAULT_BUFFER_SIZE.
virtual size_t max_send_size(void) = 0;
};
class VirtWaylandChannel : public WaylandChannel {
public:
VirtWaylandChannel() : virtwl_{-1}, supports_dmabuf_(false) {}
~VirtWaylandChannel();
int32_t init() override;
bool supports_dmabuf() override;
int32_t create_context(int& out_channel_fd) override;
int32_t create_pipe(int& out_pipe_fd) override;
int32_t send(const struct WaylandSendReceive& send) override;
int32_t handle_channel_event(enum WaylandChannelEvent& event_type,
struct WaylandSendReceive& receive,
int& out_read_pipe) override;
int32_t allocate(const struct WaylandBufferCreateInfo& create_info,
struct WaylandBufferCreateOutput& create_output) override;
int32_t sync(int dmabuf_fd, uint64_t flags) override;
int32_t handle_pipe(int read_fd, bool readable, bool& hang_up) override;
size_t max_send_size(void) override;
private:
// virtwl device file descriptor
int32_t virtwl_;
bool supports_dmabuf_;
};
class VirtGpuChannel : public WaylandChannel {
public:
VirtGpuChannel()
: virtgpu_{-1},
ring_addr_{MAP_FAILED},
ring_handle_{0},
supports_dmabuf_(false),
descriptor_id_{1} {}
~VirtGpuChannel();
int32_t init() override;
bool supports_dmabuf() override;
int32_t create_context(int& out_channel_fd) override;
int32_t create_pipe(int& out_pipe_fd) override;
int32_t send(const struct WaylandSendReceive& send) override;
int32_t handle_channel_event(enum WaylandChannelEvent& event_type,
struct WaylandSendReceive& receive,
int& out_read_pipe) override;
int32_t allocate(const struct WaylandBufferCreateInfo& create_info,
struct WaylandBufferCreateOutput& create_output) override;
int32_t sync(int dmabuf_fd, uint64_t flags) override;
int32_t handle_pipe(int read_fd, bool readable, bool& hang_up) override;
size_t max_send_size(void) override;
private:
/*
* This provides the full description of the buffer -- width, height, strides,
* offsets and host_size. Meant for internal virtgpu channel use only.
*/
struct BufferDescription {
struct WaylandBufferCreateInfo input;
struct WaylandBufferCreateOutput output;
uint32_t blob_id;
};
/*
* Provides the read end and write end of a pipe, along with the inode (a
* guest unique identifier) and host descriptor id;
*/
struct PipeDescription {
int read_fd;
int write_fd;
uint32_t identifier_type;
uint32_t inode;
uint32_t identifier;
};
int32_t image_query(const struct WaylandBufferCreateInfo& input,
struct WaylandBufferCreateOutput& output,
uint64_t& blob_id);
int32_t submit_cmd(uint32_t* cmd,
uint32_t size,
uint32_t ring_idx,
bool wait);
int32_t channel_poll(void);
int32_t close_gem_handle(uint32_t gem_handle);
int32_t create_host_blob(uint64_t blob_id, uint64_t size, int& out_fd);
int32_t fd_analysis(int fd, uint32_t& identifier, uint32_t& identifier_type);
int32_t create_fd(uint32_t identifier,
uint32_t identifier_type,
uint32_t identifier_size,
int& out_fd);
int32_t create_pipe_internal(int& out_pipe_fd,
uint32_t identifier,
uint32_t identifier_type);
int32_t handle_receive(enum WaylandChannelEvent& event_type,
struct WaylandSendReceive& receive,
int& out_read_pipe);
int32_t handle_read(void);
int32_t pipe_lookup(uint32_t identifier_type,
uint32_t& identifier,
int& fd,
size_t& index);
int32_t virtgpu_;
void* ring_addr_;
uint32_t ring_handle_;
bool supports_dmabuf_;
// Matches the crosvm-side descriptor_id, must be an odd number.
uint32_t descriptor_id_;
std::vector<BufferDescription> description_cache_;
std::vector<PipeDescription> pipe_cache_;
};
int open_virtgpu(char** drm_device);
#endif // VM_TOOLS_SOMMELIER_VIRTUALIZATION_WAYLAND_CHANNEL_H_

View File

@ -1,34 +0,0 @@
# Caution!: GYP to GN migration is happening. If you update this file, please
# update vm_tools/sommelier/wayland-protocol.gni too accordingly.
{
'variables': {
'wayland_dir': '<(SHARED_INTERMEDIATE_DIR)/<(wayland_out_dir)',
'wayland_in_dir%': '.',
},
'rules': [
{
'rule_name': 'genwayland',
'extension': 'xml',
'outputs': [
'<(wayland_dir)/<(RULE_INPUT_ROOT)-protocol.c',
'<(wayland_dir)/<(RULE_INPUT_ROOT)-client-protocol.h',
'<(wayland_dir)/<(RULE_INPUT_ROOT)-server-protocol.h',
],
'action': [
'sh',
'-c',
'wayland-scanner code < <(wayland_in_dir)/<(RULE_INPUT_NAME) > <(wayland_dir)/<(RULE_INPUT_ROOT)-protocol.c; wayland-scanner client-header < <(wayland_in_dir)/<(RULE_INPUT_NAME) > <(wayland_dir)/<(RULE_INPUT_ROOT)-client-protocol.h; wayland-scanner server-header < <(wayland_in_dir)/<(RULE_INPUT_NAME) > <(wayland_dir)/<(RULE_INPUT_ROOT)-server-protocol.h',
],
'message': 'Generating Wayland C code from <(RULE_INPUT_PATH)',
'process_outputs_as_sources': 1,
},
],
# This target exports a hard dependency because it generates header
# files.
'hard_dependency': 1,
'direct_dependent_settings': {
'include_dirs': [
'<(wayland_dir)',
],
},
}

View File

@ -1,4 +1,4 @@
# Copyright 2019 The Chromium OS Authors. All rights reserved.
# Copyright 2019 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
@ -37,9 +37,7 @@ template("wayland_protocol_library") {
sources = invoker.sources
script = "//common-mk/file_generator_wrapper.py"
output_file = "${wayland_dir}/{{source_name_part}}${g.output_suffix}"
outputs = [
output_file,
]
outputs = [ output_file ]
args = [
"wayland-scanner",
g.subcommand,

View File

@ -0,0 +1,65 @@
// Copyright 2022 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef VM_TOOLS_SOMMELIER_WEAK_RESOURCE_PTR_H_
#define VM_TOOLS_SOMMELIER_WEAK_RESOURCE_PTR_H_
#include "sommelier.h" // NOLINT(build/include_directory)
#include <algorithm>
#include <functional>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
// WeakResourcePtr is a weak pointer for a proxy object (sl_host_foo). It can
// be useful for objects with events like enter/leave, to keep track of an
// object provided in the enter event and ensure that it is not used if the
// client destroys it.
template <typename SlHostType>
class WeakResourcePtr {
public:
WeakResourcePtr() {
wl_list_init(&destroy_listener_.link);
destroy_listener_.notify = ResourceDestroyed;
}
~WeakResourcePtr() { wl_list_remove(&destroy_listener_.link); }
WeakResourcePtr& operator=(SlHostType* host) {
if (host == host_)
return *this;
Reset();
if (host) {
host_ = host;
wl_resource_add_destroy_listener(host_->resource, &destroy_listener_);
}
return *this;
}
operator bool() const { return host_; }
SlHostType* operator->() const { return host_; }
SlHostType* get() const { return host_; }
void Reset() {
host_ = nullptr;
// Remove the listener
wl_list_remove(&destroy_listener_.link);
wl_list_init(&destroy_listener_.link);
}
private:
SlHostType* host_ = nullptr;
// This is always in an initialized state
wl_listener destroy_listener_;
static void ResourceDestroyed(wl_listener* listener, void* data) {
WeakResourcePtr* ptr;
ptr = wl_container_of(listener, ptr, destroy_listener_);
ptr->Reset();
}
};
#endif // VM_TOOLS_SOMMELIER_WEAK_RESOURCE_PTR_H_